mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-11-09 20:00:12 +00:00
Compare commits
No commits in common. "2d443fb50b21032e5ffbadbb6b2cfade4d340e7c" and "95db48d8f8b903df9103251b1482e1172b53545e" have entirely different histories.
2d443fb50b
...
95db48d8f8
15
css/base.css
15
css/base.css
@ -135,10 +135,6 @@ 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;
|
||||||
@ -274,10 +270,6 @@ sup.\~critical, .text-critical {
|
|||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overflow {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
border: 0 solid var(--color-neutral-300);
|
border: 0 solid var(--color-neutral-300);
|
||||||
@ -295,9 +287,4 @@ p.top {
|
|||||||
margin-top: 0px;
|
margin-top: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#notification-box {
|
|
||||||
position: fixed;
|
|
||||||
right: 1rem;
|
|
||||||
bottom: 1rem;
|
|
||||||
z-index: 16;
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
.modal {
|
.modal {
|
||||||
display: none;
|
display: none;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
z-index: 12;
|
z-index: 1;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -16,17 +16,9 @@
|
|||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
194
html/admin.html
194
html/admin.html
@ -15,6 +15,7 @@
|
|||||||
<span class="heading">Login</span>
|
<span class="heading">Login</span>
|
||||||
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="username" id="login-user">
|
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="username" id="login-user">
|
||||||
<input type="password" class="field input ~neutral !high mb-1" placeholder="password" id="login-password">
|
<input type="password" class="field input ~neutral !high mb-1" placeholder="password" id="login-password">
|
||||||
|
<aside class="aside sm ~critical mb-half unfocused"></aside>
|
||||||
<input type="submit" class="button ~urge !normal full-width center supra submit" value="Login">
|
<input type="submit" class="button ~urge !normal full-width center supra submit" value="Login">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -29,7 +30,7 @@
|
|||||||
<div id="modal-about" class="modal">
|
<div id="modal-about" class="modal">
|
||||||
<div class="modal-content content card">
|
<div class="modal-content content card">
|
||||||
<span class="heading">About <span class="modal-close">×</span></span>
|
<span class="heading">About <span class="modal-close">×</span></span>
|
||||||
<img src="/banner.svg" class="mt-1" alt="jfa-go banner">
|
<img src="images/banner.svg" class="mt-1" alt="jfa-go banner">
|
||||||
<p><i class="icon ri-github-fill"></i><a href="https://github.com/hrfee/jfa-go">jfa-go</a></p>
|
<p><i class="icon ri-github-fill"></i><a href="https://github.com/hrfee/jfa-go">jfa-go</a></p>
|
||||||
<p>Version <span class="code monospace">{{ .version }}</span></p>
|
<p>Version <span class="code monospace">{{ .version }}</span></p>
|
||||||
<p>Commit <span class="code monospace">{{ .commit }}</span></p>
|
<p>Commit <span class="code monospace">{{ .commit }}</span></p>
|
||||||
@ -112,7 +113,6 @@
|
|||||||
<input type="submit" class="button ~urge !normal full-width center supra submit" value="Submit">
|
<input type="submit" class="button ~urge !normal full-width center supra submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="notification-box"></div>
|
|
||||||
<div class="page-container max-w-screen-lg px-6 py-4 mx-auto lg:mx-auto md:py-8">
|
<div class="page-container max-w-screen-lg px-6 py-4 mx-auto lg:mx-auto md:py-8">
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
<header class="flex flex-wrap items-center justify-between">
|
<header class="flex flex-wrap items-center justify-between">
|
||||||
@ -125,61 +125,171 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
<div class="text-neutral-700">
|
<div class="text-neutral-700">
|
||||||
<span class="button ~critical !normal mb-1 unfocused" id="logout-button">Logout</span>
|
<span class="button ~critical !normal mb-1">Logout</span>
|
||||||
<span id="button-theme" class="button ~neutral !normal mb-1">Theme</span>
|
<span id="button-theme" class="button ~neutral !normal mb-1">Theme</span>
|
||||||
|
<span id="modalButton" class="button ~neutral !normal mb-1">Trigger Login</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
<div class="inv">
|
||||||
|
<div class="card ~neutral !normal inv-header flex-expand mt-half">
|
||||||
|
<div class="inv-codearea">
|
||||||
|
<a href="#" class="code monospace mr-1">ZD8ZeC55Jcpmbtv54FuVM3</a>
|
||||||
|
<span class="button ~info !normal">Copy</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card ~neutral !low">
|
<div class="inv-infoarea">
|
||||||
<span class="heading">Create</span>
|
<span class="inv-expiry mr-1">Expires in 30m</span>
|
||||||
<div class="row" id="create-inv">
|
<span class="button ~critical !normal">Delete</span>
|
||||||
<div class="card ~neutral !normal col">
|
<label>
|
||||||
<label class="label supra" for="create-days">Days</label>
|
<i class="icon ri-arrow-down-s-line not-rotated"></i>
|
||||||
<div class="select ~neutral !normal mb-1 mt-half">
|
<input type="checkbox" class="toggle-details unfocused">
|
||||||
<select id="create-days">
|
|
||||||
<option>0</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<label class="label supra" for="create-hours">Hours</label>
|
|
||||||
<div class="select ~neutral !normal mb-1 mt-half">
|
|
||||||
<select id="create-hours">
|
|
||||||
<option>0</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<label class="label supra" for="create-minutes">Minutes</label>
|
|
||||||
<div class="select ~neutral !normal mb-1 mt-half">
|
|
||||||
<select id="create-minutes">
|
|
||||||
<option>0</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="card ~neutral !normal col">
|
|
||||||
<label class="label supra" for="create-uses">Number of uses</label>
|
|
||||||
<div class="flex-expand mb-1 mt-half">
|
|
||||||
<input type="number" min="0" id="create-uses" class="input ~neutral !normal mr-1" value=1>
|
|
||||||
<label for="create-inf-uses" class="button ~neutral !normal">
|
|
||||||
<span>∞</span>
|
|
||||||
<input type="checkbox" class="unfocused" id="create-inf-uses" aria-label="Set uses to infinite">
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<p class="support unfocused" id="create-inf-uses-warning"><span class="badge ~critical">Warning</span> invites with infinite uses can be used abusively.</p>
|
</div>
|
||||||
<label class="label supra">Profile</label>
|
<div class="card ~neutral !normal mt-half inv-details mt-half unfocused">
|
||||||
|
<div class="inv-row flex-expand align-top">
|
||||||
|
<div class="inv-profilearea">
|
||||||
|
<p class="supra mb-1 top">Profile</p>
|
||||||
|
<div class="select ~neutral !normal inv-profileselect inline-block">
|
||||||
|
<select>
|
||||||
|
<option>Friends</option>
|
||||||
|
<option>Family</option>
|
||||||
|
<option>No Profile</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p class="label supra">Notify on:</p>
|
||||||
|
<label class="switch block">
|
||||||
|
<input type="checkbox" class="inv-notify-expiry">
|
||||||
|
<span>On expiry</span>
|
||||||
|
</label>
|
||||||
|
<label class="switch block">
|
||||||
|
<input type="checkbox" class="inv-notify-creation">
|
||||||
|
<span>On user creation</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<p class="supra mb-1 top">Created <strong class="inv-created">10/12/20 18:46</strong></p>
|
||||||
|
<p class="supra mb-1">Remaining uses <strong class="inv-remaining">8</strong></p>
|
||||||
|
</div>
|
||||||
|
<div class="card ~neutral !low inv-created-users">
|
||||||
|
<strong class="supra table-header">Created users</strong>
|
||||||
|
<table class="table inv-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Date</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>jeff</td>
|
||||||
|
<td>10/12/20 19:00</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="inv">
|
||||||
|
<div class="card ~neutral !normal inv-header flex-expand mt-half">
|
||||||
|
<div class="inv-codearea">
|
||||||
|
<a href="#" class="code monospace mr-1">ZD8ZeC55Jcpmbtv54FuVM3</a>
|
||||||
|
<span class="button ~info !normal">Copy</span>
|
||||||
|
</div>
|
||||||
|
<div class="inv-infoarea">
|
||||||
|
<span class="inv-expiry mr-1">Expires in 30m</span>
|
||||||
|
<span class="button ~critical !normal">Delete</span>
|
||||||
|
<label>
|
||||||
|
<i class="icon ri-arrow-down-s-line not-rotated"></i>
|
||||||
|
<input type="checkbox" class="toggle-details unfocused">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card ~neutral !normal mt-half inv-details mt-half unfocused">
|
||||||
|
<div class="inv-row flex-expand align-top">
|
||||||
|
<div class="inv-profilearea">
|
||||||
|
<p class="supra mb-1 top">Profile</p>
|
||||||
|
<div class="select ~neutral !normal inv-profileselect inline-block">
|
||||||
|
<select>
|
||||||
|
<option>Friends</option>
|
||||||
|
<option>Family</option>
|
||||||
|
<option>No Profile</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<p class="label supra">Notify on:</p>
|
||||||
|
<label class="switch block">
|
||||||
|
<input type="checkbox" class="inv-notify-expiry">
|
||||||
|
<span>On expiry</span>
|
||||||
|
</label>
|
||||||
|
<label class="switch block">
|
||||||
|
<input type="checkbox" class="inv-notify-creation">
|
||||||
|
<span>On user creation</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="block">
|
||||||
|
<p class="supra mb-1 top">Created <strong class="inv-created">10/12/20 18:46</strong></p>
|
||||||
|
<p class="supra mb-1">Remaining uses <strong class="inv-remaining">8</strong></p>
|
||||||
|
</div>
|
||||||
|
<div class="card ~neutral !low inv-created-users empty">
|
||||||
|
<strong class="supra table-header">Created users</strong>
|
||||||
|
<p class="content">None yet!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card ~neutral !low create-inv">
|
||||||
|
<span class="heading">Create</span>
|
||||||
|
<div class="row">
|
||||||
|
<div class="card ~neutral !normal col">
|
||||||
|
<label class="label supra" for="inv-days">Days</label>
|
||||||
<div class="select ~neutral !normal mb-1 mt-half">
|
<div class="select ~neutral !normal mb-1 mt-half">
|
||||||
<select id="create-profile">
|
<select id="inv-days">
|
||||||
|
<option>0</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label class="label supra" for="inv-hours">Hours</label>
|
||||||
|
<div class="select ~neutral !normal mb-1 mt-half">
|
||||||
|
<select id="inv-hours">
|
||||||
|
<option>0</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label class="label supra" for="inv-minutes">Minutes</label>
|
||||||
|
<div class="select ~neutral !normal mb-1 mt-half">
|
||||||
|
<select id="inv-minutes">
|
||||||
|
<option>0</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card ~neutral !normal col">
|
||||||
|
<label class="label supra" for="inv-uses">Number of uses</label>
|
||||||
|
<div class="flex-expand mb-1 mt-half">
|
||||||
|
<input type="number" min="0" id="inv-uses" class="input ~neutral !normal mr-1" value=1>
|
||||||
|
<label for="inv-inf-uses" class="button ~neutral !normal">
|
||||||
|
<span>∞</span>
|
||||||
|
<input type="checkbox" class="unfocused" id="inv-inf-uses" aria-label="Set uses to infinite">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<p class="support unfocused"><span class="badge ~critical">Warning</span> invites with infinite uses can be used abusively.</p>
|
||||||
|
<label class="label supra">Profile</label>
|
||||||
|
<div class="select ~neutral !normal mb-1 mt-half" id="inv-profile">
|
||||||
|
<select>
|
||||||
|
<option>Friends</option>
|
||||||
|
<option>Family</option>
|
||||||
|
<option>No Profile</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<label class="label supra">Send to</label>
|
<label class="label supra">Send to</label>
|
||||||
<div class="flex-expand mb-1 mt-half">
|
<div class="flex-expand mb-1 mt-half">
|
||||||
<input type="email" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com">
|
<input type="email" id="inv-email" class="input ~neutral !normal mr-1" placeholder="example@example.com">
|
||||||
<label for="create-send-to-enabled" class="button ~neutral !normal">
|
<label for="inv-email-enabled" class="button ~neutral !normal">
|
||||||
<input type="checkbox" id="create-send-to-enabled" aria-label="Send to address enabled">
|
<input type="checkbox" id="inv-email-enabled" aria-label="Send to address enabled">
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<span class="button ~urge !normal supra full-width center lg" id="create-submit">Create</span>
|
<span class="button ~urge !normal supra full-width center lg">Create</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -246,7 +356,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 right">
|
<div class="tooltip">
|
||||||
<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>
|
||||||
|
104
ts/admin.ts
104
ts/admin.ts
@ -1,23 +1,84 @@
|
|||||||
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, createInvite } from "./modules/invites.js";
|
|
||||||
import { _post, notificationBox, whichAnimationEvent } from "./modules/common.js";
|
|
||||||
|
|
||||||
loadTheme();
|
loadTheme();
|
||||||
(document.getElementById('button-theme') as HTMLSpanElement).onclick = toggleTheme;
|
(document.getElementById('button-theme') as HTMLSpanElement).onclick = toggleTheme;
|
||||||
|
|
||||||
|
const whichAnimationEvent = () => {
|
||||||
|
const el = document.createElement("fakeElement");
|
||||||
|
if (el.style["animation"] !== void 0) {
|
||||||
|
return "animationend";
|
||||||
|
}
|
||||||
|
return "webkitAnimationEnd";
|
||||||
|
}
|
||||||
window.animationEvent = whichAnimationEvent();
|
window.animationEvent = whichAnimationEvent();
|
||||||
|
const toggles: HTMLInputElement[] = Array.from(document.getElementsByClassName('toggle-details'));
|
||||||
|
for (let toggle of toggles) {
|
||||||
|
toggle.onclick = () => {
|
||||||
|
const el = toggle.parentElement.parentElement.parentElement.nextElementSibling as HTMLDivElement;
|
||||||
|
if (el.classList.contains("focused")) {
|
||||||
|
el.classList.toggle("focused");
|
||||||
|
el.classList.toggle("unfocused");
|
||||||
|
} else {
|
||||||
|
el.classList.toggle("unfocused");
|
||||||
|
el.classList.toggle("focused");
|
||||||
|
}
|
||||||
|
toggle.previousElementSibling.classList.toggle("rotated");
|
||||||
|
toggle.previousElementSibling.classList.toggle("not-rotated");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
window.token = "";
|
const checkInfUses = function (check: HTMLInputElement, mode = 2) {
|
||||||
|
const uses = document.getElementById('inv-uses') as HTMLInputElement;
|
||||||
|
if (mode == 2) {
|
||||||
|
uses.disabled = check.checked;
|
||||||
|
check.parentElement.classList.toggle('~neutral');
|
||||||
|
check.parentElement.classList.toggle('~urge');
|
||||||
|
check.parentElement.parentElement.nextElementSibling.classList.toggle('unfocused');
|
||||||
|
} else if (mode == 1) {
|
||||||
|
uses.disabled = true;
|
||||||
|
check.checked = true;
|
||||||
|
check.parentElement.classList.remove('~neutral');
|
||||||
|
check.parentElement.classList.add('~urge');
|
||||||
|
check.parentElement.parentElement.nextElementSibling.classList.remove('unfocused');
|
||||||
|
} else {
|
||||||
|
uses.disabled = false;
|
||||||
|
check.checked = false;
|
||||||
|
check.parentElement.classList.remove('~urge');
|
||||||
|
check.parentElement.classList.add('~neutral');
|
||||||
|
check.parentElement.parentElement.nextElementSibling.classList.add('unfocused');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.availableProfiles = window.availableProfiles || [];
|
let invInfUses = document.getElementById('inv-inf-uses') as HTMLInputElement;
|
||||||
|
invInfUses.onclick = () => { checkInfUses(invInfUses, 2); };
|
||||||
|
|
||||||
var inviteCreator = new createInvite();
|
const checkEmailEnabled = function (check: HTMLInputElement, mode = 2) {
|
||||||
|
const input = document.getElementById('inv-email') as HTMLInputElement;
|
||||||
|
if (mode == 2) {
|
||||||
|
input.disabled = !check.checked;
|
||||||
|
check.parentElement.classList.toggle('~neutral');
|
||||||
|
check.parentElement.classList.toggle('~urge');
|
||||||
|
} else if (mode == 1) {
|
||||||
|
input.disabled = false;
|
||||||
|
check.checked = true;
|
||||||
|
check.parentElement.classList.remove('~neutral');
|
||||||
|
check.parentElement.classList.add('~urge');
|
||||||
|
} else {
|
||||||
|
input.disabled = true;
|
||||||
|
check.checked = false;
|
||||||
|
check.parentElement.classList.remove('~urge');
|
||||||
|
check.parentElement.classList.add('~neutral');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.invites = new inviteList();
|
let invEmailEnabled = document.getElementById('inv-email-enabled') as HTMLInputElement;
|
||||||
|
invEmailEnabled.onchange = () => { checkEmailEnabled(invEmailEnabled, 2); };
|
||||||
|
|
||||||
|
checkInfUses(invInfUses, 0);
|
||||||
|
checkEmailEnabled(invEmailEnabled, 0);
|
||||||
|
|
||||||
window.notifications = new notificationBox(document.getElementById('notification-box') as HTMLDivElement, 5);
|
|
||||||
|
|
||||||
|
|
||||||
const loadAccounts = function () {
|
const loadAccounts = function () {
|
||||||
@ -125,6 +186,7 @@ window.tabs.addTab("accountsTab", loadAccounts);
|
|||||||
window.modals = {} as Modals;
|
window.modals = {} as Modals;
|
||||||
|
|
||||||
window.modals.login = new Modal(document.getElementById('modal-login'), true);
|
window.modals.login = new Modal(document.getElementById('modal-login'), true);
|
||||||
|
document.getElementById('modalButton').onclick = window.modals.login.toggle;
|
||||||
|
|
||||||
window.modals.addUser = new Modal(document.getElementById('modal-add-user'));
|
window.modals.addUser = new Modal(document.getElementById('modal-add-user'));
|
||||||
(document.getElementById('accounts-add-user') as HTMLSpanElement).onclick = window.modals.addUser.toggle;
|
(document.getElementById('accounts-add-user') as HTMLSpanElement).onclick = window.modals.addUser.toggle;
|
||||||
@ -149,6 +211,15 @@ window.tabs.addTab("accountsTab", loadAccounts);
|
|||||||
document.getElementById('form-ombi-defaults').addEventListener('submit', window.modals.ombiDefaults.close);
|
document.getElementById('form-ombi-defaults').addEventListener('submit', window.modals.ombiDefaults.close);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
function errorMessage(aside: HTMLElement, content: string, timeout: number = 4) {
|
||||||
|
aside.textContent = content;
|
||||||
|
aside.classList.remove("unfocused");
|
||||||
|
setTimeout(() => { aside.classList.add("unfocused"); }, timeout*1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.token = "";
|
||||||
|
|
||||||
function login(username: string, password: string) {
|
function login(username: string, password: string) {
|
||||||
const req = new XMLHttpRequest();
|
const req = new XMLHttpRequest();
|
||||||
req.responseType = 'json';
|
req.responseType = 'json';
|
||||||
@ -174,7 +245,8 @@ function login(username: string, password: string) {
|
|||||||
errorMsg = "Unknown error";
|
errorMsg = "Unknown error";
|
||||||
}
|
}
|
||||||
if (!refresh) {
|
if (!refresh) {
|
||||||
window.notifications.customError("loginError", errorMsg);
|
errorMessage(window.modals.login.modal.querySelector("aside"), errorMsg, 5);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
window.modals.login.show();
|
window.modals.login.show();
|
||||||
}
|
}
|
||||||
@ -182,10 +254,6 @@ 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);
|
|
||||||
document.getElementById("logout-button").classList.remove("unfocused");
|
|
||||||
|
|
||||||
/*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);
|
||||||
@ -213,18 +281,10 @@ document.getElementById('form-login').addEventListener('submit', (event: Event)
|
|||||||
const username = (document.getElementById("login-user") as HTMLInputElement).value;
|
const username = (document.getElementById("login-user") as HTMLInputElement).value;
|
||||||
const password = (document.getElementById("login-password") as HTMLInputElement).value;
|
const password = (document.getElementById("login-password") as HTMLInputElement).value;
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
window.notifications.customError("loginError", "The username and/or password were left blank.");
|
errorMessage(window.modals.login.modal.querySelector("aside"), "The username and/or password were left blank." , 4);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
login(username, password);
|
login(username, password, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
login("", "");
|
login("", "");
|
||||||
|
|
||||||
(document.getElementById('logout-button') as HTMLButtonElement).onclick = () => _post("/logout", null, (req: XMLHttpRequest): boolean => {
|
|
||||||
if (req.readyState == 4 && req.status == 200) {
|
|
||||||
window.token = "";
|
|
||||||
location.reload();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
@ -49,25 +49,18 @@ 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: (req: XMLHttpRequest) => void): void => {
|
|
||||||
|
export const _get = (url: string, data: Object, onreadystatechange: () => 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 = () => {
|
req.onreadystatechange = onreadystatechange;
|
||||||
if (req.status == 0) {
|
|
||||||
window.notifications.connectionError();
|
|
||||||
return;
|
|
||||||
} else if (req.status == 401) {
|
|
||||||
window.notifications.customError("401Error", "Unauthorized. Try logging back in.");
|
|
||||||
}
|
|
||||||
onreadystatechange(req);
|
|
||||||
};
|
|
||||||
req.send(JSON.stringify(data));
|
req.send(JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void, response?: boolean): void => {
|
export const _post = (url: string, data: Object, onreadystatechange: () => void, response?: boolean): void => {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("POST", window.URLBase + url, true);
|
req.open("POST", window.URLBase + url, true);
|
||||||
if (response) {
|
if (response) {
|
||||||
@ -75,90 +68,16 @@ export const _post = (url: string, data: Object, onreadystatechange: (req: XMLHt
|
|||||||
}
|
}
|
||||||
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 = () => {
|
req.onreadystatechange = onreadystatechange;
|
||||||
if (req.status == 0) {
|
|
||||||
window.notifications.connectionError();
|
|
||||||
return;
|
|
||||||
} else if (req.status == 401) {
|
|
||||||
window.notifications.customError("401Error", "Unauthorized. Try logging back in.");
|
|
||||||
}
|
|
||||||
onreadystatechange(req);
|
|
||||||
};
|
|
||||||
req.send(JSON.stringify(data));
|
req.send(JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
export function _delete(url: string, data: Object, onreadystatechange: (req: XMLHttpRequest) => void): void {
|
export function _delete(url: string, data: Object, onreadystatechange: () => void): void {
|
||||||
let req = new XMLHttpRequest();
|
let req = new XMLHttpRequest();
|
||||||
req.open("DELETE", window.URLBase + url, true);
|
req.open("DELETE", window.URLBase + url, true);
|
||||||
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 = () => {
|
req.onreadystatechange = onreadystatechange;
|
||||||
if (req.status == 0) {
|
|
||||||
window.notifications.connectionError();
|
|
||||||
return;
|
|
||||||
} else if (req.status == 401) {
|
|
||||||
window.notifications.customError("401Error", "Unauthorized. Try logging back in.");
|
|
||||||
}
|
|
||||||
onreadystatechange(req);
|
|
||||||
};
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class notificationBox implements NotificationBox {
|
|
||||||
private _box: HTMLDivElement;
|
|
||||||
private _errorTypes: { [type: string]: boolean } = {};
|
|
||||||
timeout: number;
|
|
||||||
constructor(box: HTMLDivElement, timeout?: number) { this._box = box; this.timeout = timeout || 5; }
|
|
||||||
|
|
||||||
private _error = (message: string): HTMLElement => {
|
|
||||||
const noti = document.createElement('aside');
|
|
||||||
noti.classList.add("aside", "~critical", "!normal", "mt-half", "notification-error");
|
|
||||||
noti.innerHTML = `<strong>Error:</strong> ${message}`;
|
|
||||||
const closeButton = document.createElement('span') as HTMLSpanElement;
|
|
||||||
closeButton.classList.add("button", "~critical", "!low", "ml-1");
|
|
||||||
closeButton.innerHTML = `<i class="icon ri-close-line"></i>`;
|
|
||||||
closeButton.onclick = () => { this._box.removeChild(noti); };
|
|
||||||
noti.appendChild(closeButton);
|
|
||||||
return noti;
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionError = () => { this.customError("connectionError", "Couldn't connect to jfa-go"); }
|
|
||||||
|
|
||||||
customError = (type: string, message: string) => {
|
|
||||||
this._errorTypes[type] = this._errorTypes[type] || false;
|
|
||||||
const noti = this._error(message);
|
|
||||||
noti.classList.add("error-" + type);
|
|
||||||
const previousNoti: HTMLElement | undefined = this._box.querySelector("aside.error-" + type);
|
|
||||||
if (this._errorTypes[type] && previousNoti !== undefined && previousNoti != null) {
|
|
||||||
previousNoti.remove();
|
|
||||||
}
|
|
||||||
this._box.appendChild(noti);
|
|
||||||
this._errorTypes[type] = true;
|
|
||||||
setTimeout(() => { if (this._box.contains(noti)) { this._box.removeChild(noti); this._errorTypes[type] = false; } }, this.timeout*1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const whichAnimationEvent = () => {
|
|
||||||
const el = document.createElement("fakeElement");
|
|
||||||
if (el.style["animation"] !== void 0) {
|
|
||||||
return "animationend";
|
|
||||||
}
|
|
||||||
return "webkitAnimationEnd";
|
|
||||||
}
|
|
||||||
|
@ -1,596 +0,0 @@
|
|||||||
import { _get, _post, _delete, toClipboard } from "../modules/common.js";
|
|
||||||
|
|
||||||
export class DOMInvite implements Invite {
|
|
||||||
// TODO
|
|
||||||
// add setProfile
|
|
||||||
//
|
|
||||||
updateNotify = (checkbox: HTMLInputElement) => {
|
|
||||||
let state: { [code: string]: { [type: string]: boolean } } = {};
|
|
||||||
let revertChanges: () => void;
|
|
||||||
if (checkbox.classList.contains("inv-notify-expiry")) {
|
|
||||||
revertChanges = () => { this.notifyExpiry = !this.notifyExpiry };
|
|
||||||
state[this.code] = { "notify-expiry": this.notifyExpiry };
|
|
||||||
} else {
|
|
||||||
revertChanges = () => { this.notifyCreation = !this.notifyCreation };
|
|
||||||
state[this.code] = { "notify-creation": this.notifyCreation };
|
|
||||||
}
|
|
||||||
_post("/invites/notify", state, (req: XMLHttpRequest) => {
|
|
||||||
if (req.readyState == 4 && !(req.status == 200 || req.status == 204)) {
|
|
||||||
revertChanges();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
delete = () => _delete("/invites", { "code": this.code }, (req: XMLHttpRequest) => {
|
|
||||||
if (req.readyState == 4 && (req.status == 200 || req.status == 204)) {
|
|
||||||
this.remove();
|
|
||||||
const inviteDeletedEvent = new CustomEvent("inviteDeletedEvent", { "detail": this.code });
|
|
||||||
document.dispatchEvent(inviteDeletedEvent);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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;
|
|
||||||
this._profile = selected;
|
|
||||||
};
|
|
||||||
updateProfile = () => {
|
|
||||||
const select = this._left.querySelector("select") as HTMLSelectElement;
|
|
||||||
const previous = this.profile;
|
|
||||||
let profile = select.value;
|
|
||||||
if (profile == "noProfile") { profile = ""; }
|
|
||||||
_post("/invites/profile", { "invite": this.code, "profile": profile }, (req: XMLHttpRequest) => {
|
|
||||||
if (req.readyState == 4) {
|
|
||||||
if (!(req.status == 200 || req.status == 204)) {
|
|
||||||
select.value = previous || "noProfile";
|
|
||||||
} else {
|
|
||||||
this._profile = profile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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", "overflow");
|
|
||||||
|
|
||||||
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>
|
|
||||||
`;
|
|
||||||
const copyButton = this._codeArea.querySelector("span.button") as HTMLSpanElement;
|
|
||||||
copyButton.onclick = () => {
|
|
||||||
toClipboard(this._codeLink);
|
|
||||||
const icon = copyButton.children[0];
|
|
||||||
icon.classList.remove("ri-file-copy-line");
|
|
||||||
icon.classList.add("ri-check-line");
|
|
||||||
copyButton.classList.remove("~info");
|
|
||||||
copyButton.classList.add("~positive");
|
|
||||||
setTimeout(() => {
|
|
||||||
icon.classList.remove("ri-check-line");
|
|
||||||
icon.classList.add("ri-file-copy-line");
|
|
||||||
copyButton.classList.remove("~positive");
|
|
||||||
copyButton.classList.add("~info");
|
|
||||||
}, 800);
|
|
||||||
};
|
|
||||||
|
|
||||||
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._left.querySelector("select") as HTMLSelectElement).onchange = this.updateProfile;
|
|
||||||
|
|
||||||
const notifyExpiry = this._left.querySelector("input.inv-notify-expiry") as HTMLInputElement;
|
|
||||||
notifyExpiry.onchange = () => { this._notifyExpiry = notifyExpiry.checked; this.updateNotify(notifyExpiry); };
|
|
||||||
|
|
||||||
const notifyCreation = this._left.querySelector("input.inv-notify-creation") as HTMLInputElement;
|
|
||||||
notifyCreation.onchange = () => { this._notifyCreation = notifyCreation.checked; this.updateNotify(notifyCreation); };
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
document.addEventListener("profileLoadEvent", () => { this.loadProfiles(); }, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
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(); }
|
|
||||||
}
|
|
||||||
|
|
||||||
export class inviteList implements inviteList {
|
|
||||||
private _list: HTMLDivElement;
|
|
||||||
private _empty: boolean;
|
|
||||||
// since invite reload sends profiles, this event it broadcast so the createInvite object can load them.
|
|
||||||
private _profileLoadEvent = new CustomEvent("profileLoadEvent");
|
|
||||||
|
|
||||||
invites: { [code: string]: DOMInvite };
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this._list = document.getElementById('invites') as HTMLDivElement;
|
|
||||||
this.empty = true;
|
|
||||||
this.invites = {};
|
|
||||||
document.addEventListener("newInviteEvent", () => { this.reload(); }, false);
|
|
||||||
document.addEventListener("inviteDeletedEvent", (event: CustomEvent) => {
|
|
||||||
const code = event.detail;
|
|
||||||
const length = Object.keys(this.invites).length - 1; // store prior as Object.keys is undefined when there are no keys
|
|
||||||
delete this.invites[code];
|
|
||||||
if (length == 0) {
|
|
||||||
this.empty = true;
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
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"];
|
|
||||||
document.dispatchEvent(this._profileLoadEvent);
|
|
||||||
if (data["invites"] === undefined || data["invites"] == null || 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class createInvite {
|
|
||||||
private _sendToEnabled = document.getElementById("create-send-to-enabled") as HTMLInputElement;
|
|
||||||
private _sendTo = document.getElementById("create-send-to") as HTMLInputElement;
|
|
||||||
private _uses = document.getElementById('create-uses') as HTMLInputElement;
|
|
||||||
private _infUses = document.getElementById("create-inf-uses") as HTMLInputElement;
|
|
||||||
private _infUsesWarning = document.getElementById('create-inf-uses-warning') as HTMLParagraphElement;
|
|
||||||
private _createButton = document.getElementById("create-submit") as HTMLInputElement; // Actually a <span> but this allows "disabled"
|
|
||||||
private _profile = document.getElementById("create-profile") as HTMLSelectElement;
|
|
||||||
// Broadcast when new invite created
|
|
||||||
private _newInviteEvent = new CustomEvent("newInviteEvent");
|
|
||||||
private _firstLoad = true;
|
|
||||||
|
|
||||||
private _count: Number = 30;
|
|
||||||
private _populateNumbers = () => {
|
|
||||||
const fieldIDs = ["create-days", "create-hours", "create-minutes"];
|
|
||||||
for (let i = 0; i < fieldIDs.length; i++) {
|
|
||||||
const field = document.getElementById(fieldIDs[i]);
|
|
||||||
field.textContent = '';
|
|
||||||
for (let n = 0; n <= this._count; n++) {
|
|
||||||
const opt = document.createElement("option") as HTMLOptionElement;
|
|
||||||
opt.textContent = ""+n;
|
|
||||||
opt.value = ""+n;
|
|
||||||
field.appendChild(opt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get sendToEnabled(): boolean {
|
|
||||||
return this._sendToEnabled.checked;
|
|
||||||
}
|
|
||||||
set sendToEnabled(state: boolean) {
|
|
||||||
this._sendToEnabled.checked = state;
|
|
||||||
this._sendTo.disabled = !state;
|
|
||||||
if (state) {
|
|
||||||
this._sendToEnabled.parentElement.classList.remove("~neutral");
|
|
||||||
this._sendToEnabled.parentElement.classList.add("~urge");
|
|
||||||
} else {
|
|
||||||
this._sendToEnabled.parentElement.classList.remove("~urge");
|
|
||||||
this._sendToEnabled.parentElement.classList.add("~neutral");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get infiniteUses(): boolean {
|
|
||||||
return this._infUses.checked;
|
|
||||||
}
|
|
||||||
set infiniteUses(state: boolean) {
|
|
||||||
this._infUses.checked = state;
|
|
||||||
this._uses.disabled = state;
|
|
||||||
if (state) {
|
|
||||||
this._infUses.parentElement.classList.remove("~neutral");
|
|
||||||
this._infUses.parentElement.classList.add("~urge");
|
|
||||||
this._infUsesWarning.classList.remove("unfocused");
|
|
||||||
} else {
|
|
||||||
this._infUses.parentElement.classList.remove("~urge");
|
|
||||||
this._infUses.parentElement.classList.add("~neutral");
|
|
||||||
this._infUsesWarning.classList.add("unfocused");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get uses(): number { return this._uses.valueAsNumber; }
|
|
||||||
set uses(n: number) { this._uses.valueAsNumber = n; }
|
|
||||||
|
|
||||||
private _checkDurationValidity = () => {
|
|
||||||
this._createButton.disabled = (this.days + this.hours + this.minutes == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
get days(): number {
|
|
||||||
return +(document.getElementById("create-days") as HTMLSelectElement).value;
|
|
||||||
}
|
|
||||||
set days(n: number) {
|
|
||||||
(document.getElementById("create-days") as HTMLSelectElement).value = ""+n;
|
|
||||||
this._checkDurationValidity();
|
|
||||||
}
|
|
||||||
get hours(): number {
|
|
||||||
return +(document.getElementById("create-hours") as HTMLSelectElement).value;
|
|
||||||
}
|
|
||||||
set hours(n: number) {
|
|
||||||
(document.getElementById("create-hours") as HTMLSelectElement).value = ""+n;
|
|
||||||
this._checkDurationValidity();
|
|
||||||
}
|
|
||||||
get minutes(): number {
|
|
||||||
return +(document.getElementById("create-minutes") as HTMLSelectElement).value;
|
|
||||||
}
|
|
||||||
set minutes(n: number) {
|
|
||||||
(document.getElementById("create-minutes") as HTMLSelectElement).value = ""+n;
|
|
||||||
this._checkDurationValidity();
|
|
||||||
}
|
|
||||||
|
|
||||||
get sendTo(): string { return this._sendTo.value; }
|
|
||||||
set sendTo(address: string) { this._sendTo.value = address; }
|
|
||||||
|
|
||||||
get profile(): string {
|
|
||||||
const val = this._profile.value;
|
|
||||||
if (val == "noProfile") {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
set profile(p: string) {
|
|
||||||
if (p == "") { p = "noProfile"; }
|
|
||||||
this._profile.value = p;
|
|
||||||
}
|
|
||||||
|
|
||||||
loadProfiles = () => {
|
|
||||||
let innerHTML = `<option value="noProfile">No Profile</option>`;
|
|
||||||
for (let profile of window.availableProfiles) {
|
|
||||||
innerHTML += `<option value="${profile}">${profile}</option>`;
|
|
||||||
}
|
|
||||||
let selected = this.profile;
|
|
||||||
this._profile.innerHTML = innerHTML;
|
|
||||||
if (this._firstLoad) {
|
|
||||||
this.profile = window.availableProfiles[0] || "";
|
|
||||||
this._firstLoad = false;
|
|
||||||
} else {
|
|
||||||
this.profile = selected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
create = () => {
|
|
||||||
let send = {
|
|
||||||
"days": this.days,
|
|
||||||
"hours": this.hours,
|
|
||||||
"minutes": this.minutes,
|
|
||||||
"multiple-uses": (this.uses > 1 || this.infiniteUses),
|
|
||||||
"no-limit": this.infiniteUses,
|
|
||||||
"remaining-uses": this.uses,
|
|
||||||
"email": this.sendToEnabled ? this.sendTo : "",
|
|
||||||
"profile": this.profile
|
|
||||||
};
|
|
||||||
_post("/invites", send, (req: XMLHttpRequest) => {
|
|
||||||
if (req.readyState == 4 && (req.status == 200 || req.status == 204)) {
|
|
||||||
document.dispatchEvent(this._newInviteEvent);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this._populateNumbers();
|
|
||||||
this.days = 0;
|
|
||||||
this.hours = 0;
|
|
||||||
this.minutes = 30;
|
|
||||||
this._infUses.onchange = () => { this.infiniteUses = this.infiniteUses; };
|
|
||||||
this.infiniteUses = false;
|
|
||||||
this._sendToEnabled.onchange = () => { this.sendToEnabled = this.sendToEnabled; };
|
|
||||||
this.sendToEnabled = false;
|
|
||||||
this._createButton.onclick = this.create;
|
|
||||||
this.sendTo = "";
|
|
||||||
this.uses = 1;
|
|
||||||
document.addEventListener("profileLoadEvent", () => { this.loadProfiles(); }, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -14,21 +14,14 @@ declare interface Window {
|
|||||||
URLBase: string;
|
URLBase: string;
|
||||||
modals: Modals;
|
modals: Modals;
|
||||||
cssFile: string;
|
cssFile: string;
|
||||||
availableProfiles: string[];
|
availableProfiles: Array<any>;
|
||||||
jfUsers: Array<Object>;
|
jfUsers: Array<Object>;
|
||||||
notifications_enabled: boolean;
|
notifications_enabled: boolean;
|
||||||
token: string;
|
token: string;
|
||||||
buttonWidth: number;
|
buttonWidth: number;
|
||||||
transitionEvent: string;
|
transitionEvent: string;
|
||||||
animationEvent: string;
|
animationEvent: string;
|
||||||
tabs: Tabs;
|
tabs: Tabs
|
||||||
invites: inviteList;
|
|
||||||
notifications: NotificationBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface NotificationBox {
|
|
||||||
connectionError: () => void;
|
|
||||||
customError: (type: string, message: string) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare interface Tabs {
|
declare interface Tabs {
|
||||||
@ -61,21 +54,15 @@ 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?: string[][];
|
usedBy?: Array<Array<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