Compare commits
1
Commits
ead4cd7492
...
v0.5.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b4487ac2e |
@@ -0,0 +1,263 @@
|
||||
.warpbox-dialog-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 130;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 1rem;
|
||||
background: color-mix(in srgb, var(--background) 60%, transparent);
|
||||
backdrop-filter: blur(8px);
|
||||
opacity: 0;
|
||||
transition: opacity 160ms ease;
|
||||
}
|
||||
|
||||
.warpbox-dialog-overlay.is-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.warpbox-dialog {
|
||||
position: relative;
|
||||
width: min(28rem, 100%);
|
||||
max-height: min(34rem, 90vh);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--card);
|
||||
color: var(--card-foreground);
|
||||
box-shadow: var(--shadow);
|
||||
opacity: 0;
|
||||
transform: translateY(0.6rem) scale(0.98);
|
||||
transition: opacity 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
.warpbox-dialog:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.warpbox-dialog-overlay.is-visible .warpbox-dialog {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
.warpbox-dialog-head {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 0.85rem;
|
||||
align-items: center;
|
||||
padding: 1.1rem 3.25rem 0 1.1rem;
|
||||
}
|
||||
|
||||
.warpbox-dialog-icon {
|
||||
width: 1.9rem;
|
||||
height: 1.9rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--primary) 20%, transparent);
|
||||
color: var(--primary);
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.warpbox-dialog-warning .warpbox-dialog-icon {
|
||||
background: color-mix(in srgb, var(--primary) 26%, transparent);
|
||||
color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.warpbox-dialog-error .warpbox-dialog-icon {
|
||||
background: color-mix(in srgb, var(--danger) 18%, transparent);
|
||||
color: var(--danger);
|
||||
}
|
||||
|
||||
.warpbox-dialog-title {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.warpbox-dialog-close {
|
||||
position: absolute;
|
||||
top: 1.1rem;
|
||||
right: 1.1rem;
|
||||
z-index: 2;
|
||||
min-height: 1.9rem;
|
||||
height: 1.9rem;
|
||||
width: 1.9rem;
|
||||
padding: 0;
|
||||
border-color: var(--border);
|
||||
color: var(--muted-foreground);
|
||||
background: var(--surface-1);
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.warpbox-dialog-close:hover {
|
||||
color: var(--foreground);
|
||||
background: var(--surface-1-hover);
|
||||
}
|
||||
|
||||
.warpbox-dialog-body {
|
||||
padding: 0.85rem 1.1rem 1.1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.warpbox-dialog-message {
|
||||
margin: 0 0 0.75rem;
|
||||
color: var(--muted-foreground);
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.warpbox-dialog-message:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.warpbox-dialog-field {
|
||||
width: 100%;
|
||||
border: 1px solid var(--input);
|
||||
border-radius: calc(var(--radius) - 0.35rem);
|
||||
background: var(--surface-1);
|
||||
color: var(--foreground);
|
||||
padding: 0.55rem 0.7rem;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.warpbox-dialog-field:focus {
|
||||
outline: 2px solid var(--ring);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.warpbox-dialog-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.55rem;
|
||||
padding: 0 1.1rem 1.1rem;
|
||||
}
|
||||
|
||||
html.warpbox-dialog-open,
|
||||
html.warpbox-dialog-open body {
|
||||
overflow: hidden;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.dialog-file-list {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
max-height: 14rem;
|
||||
overflow: auto;
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.dialog-file-row {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
padding: 0.5rem 0.65rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: calc(var(--radius) - 0.35rem);
|
||||
background: var(--surface-1);
|
||||
}
|
||||
|
||||
.dialog-file-icon {
|
||||
width: 1.35rem;
|
||||
height: 1.35rem;
|
||||
color: var(--muted-foreground);
|
||||
}
|
||||
|
||||
.dialog-file-name {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.dialog-file-size {
|
||||
color: var(--muted-foreground);
|
||||
font-size: 0.8rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog {
|
||||
border: 1px solid #000000;
|
||||
border-radius: 0;
|
||||
background: #c0c0c0;
|
||||
color: #000000;
|
||||
box-shadow: inset -1px -1px 0 #404040, inset 1px 1px 0 #ffffff, inset -2px -2px 0 #808080, inset 2px 2px 0 #dfdfdf, 4px 4px 0 rgba(0, 0, 0, 0.45);
|
||||
font-family: "PixeloidSans", "PixelOperator", "Microsoft Sans Serif", Tahoma, sans-serif;
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog-head {
|
||||
padding-top: 0.2rem;
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog::before {
|
||||
content: "Warpbox";
|
||||
display: block;
|
||||
margin: 0.18rem 0.18rem 0;
|
||||
padding: 0.22rem 0.35rem;
|
||||
background: linear-gradient(to right, #000078, 80%, #0f80cd);
|
||||
color: #ffffff;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog-error::before {
|
||||
content: "Warpbox - Error";
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog-warning::before {
|
||||
content: "Warpbox - Warning";
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog-info::before {
|
||||
content: "Warpbox - Info";
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog-icon {
|
||||
border: 1px solid #000000;
|
||||
background: #ffffff;
|
||||
color: #000078;
|
||||
box-shadow: inset 1px 1px 0 #808080, inset -1px -1px 0 #ffffff;
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog-warning .warpbox-dialog-icon {
|
||||
color: #9a5b00;
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog-error .warpbox-dialog-icon {
|
||||
color: #c00000;
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog-message {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
:root[data-theme="retro"] .warpbox-dialog-close {
|
||||
top: 0.36rem;
|
||||
right: 0.3rem;
|
||||
width: 1.1rem;
|
||||
height: 0.95rem;
|
||||
min-height: 0.95rem;
|
||||
background: #c0c0c0;
|
||||
color: #000000;
|
||||
border: 1px solid #000000;
|
||||
box-shadow: inset -1px -1px 0 #404040, inset 1px 1px 0 #ffffff, inset -2px -2px 0 #808080, inset 2px 2px 0 #dfdfdf;
|
||||
font-size: 0.6rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.warpbox-dialog-overlay {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.warpbox-dialog {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
(function () {
|
||||
const VARIANTS = ["info", "warning", "error"];
|
||||
const FOCUSABLE_SELECTOR = 'a[href], button:not([disabled]), textarea:not([disabled]), input:not([disabled]), select:not([disabled]), [tabindex]:not([tabindex="-1"])';
|
||||
|
||||
window.Warpbox = window.Warpbox || {};
|
||||
let dialogIdCounter = 0;
|
||||
|
||||
function defaultTitle(variant) {
|
||||
if (variant === "error") {
|
||||
return "Error";
|
||||
}
|
||||
if (variant === "warning") {
|
||||
return "Warning";
|
||||
}
|
||||
return "Info";
|
||||
}
|
||||
|
||||
function normalizeOptions(options, message) {
|
||||
if (typeof options === "string") {
|
||||
options = { message: options };
|
||||
} else {
|
||||
options = options || {};
|
||||
}
|
||||
if (message) {
|
||||
options.message = message;
|
||||
}
|
||||
const variant = VARIANTS.includes(options.variant) ? options.variant : "info";
|
||||
return {
|
||||
variant,
|
||||
title: options.title || defaultTitle(variant),
|
||||
message: options.message || "",
|
||||
body: options.body || null,
|
||||
actions: Array.isArray(options.actions) ? options.actions : [],
|
||||
dismissible: options.dismissible !== false,
|
||||
closable: options.closable !== false,
|
||||
onClose: typeof options.onClose === "function" ? options.onClose : null,
|
||||
};
|
||||
}
|
||||
|
||||
function focusableElements(container) {
|
||||
return Array.from(container.querySelectorAll(FOCUSABLE_SELECTOR)).filter((el) => el.offsetParent !== null);
|
||||
}
|
||||
|
||||
function dialog(options, message) {
|
||||
const config = normalizeOptions(options, message);
|
||||
const previouslyFocused = document.activeElement;
|
||||
dialogIdCounter += 1;
|
||||
const titleId = "warpbox-dialog-title-" + dialogIdCounter;
|
||||
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "warpbox-dialog-overlay";
|
||||
|
||||
const card = document.createElement("div");
|
||||
card.className = "warpbox-dialog warpbox-dialog-" + config.variant;
|
||||
card.setAttribute("role", config.variant === "error" ? "alertdialog" : "dialog");
|
||||
card.setAttribute("aria-modal", "true");
|
||||
card.setAttribute("aria-labelledby", titleId);
|
||||
card.setAttribute("tabindex", "-1");
|
||||
|
||||
const head = document.createElement("div");
|
||||
head.className = "warpbox-dialog-head";
|
||||
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "warpbox-dialog-icon";
|
||||
icon.setAttribute("aria-hidden", "true");
|
||||
icon.textContent = config.variant === "error" ? "!" : config.variant === "warning" ? "?" : "i";
|
||||
|
||||
const title = document.createElement("h2");
|
||||
title.id = titleId;
|
||||
title.className = "warpbox-dialog-title";
|
||||
title.textContent = config.title;
|
||||
|
||||
head.append(icon, title);
|
||||
|
||||
if (config.closable) {
|
||||
const close = document.createElement("button");
|
||||
close.type = "button";
|
||||
close.className = "warpbox-dialog-close";
|
||||
close.setAttribute("aria-label", "Close dialog");
|
||||
close.textContent = "x";
|
||||
close.addEventListener("click", () => closeDialog());
|
||||
head.append(close);
|
||||
}
|
||||
|
||||
const body = document.createElement("div");
|
||||
body.className = "warpbox-dialog-body";
|
||||
|
||||
if (config.message) {
|
||||
const text = document.createElement("p");
|
||||
text.className = "warpbox-dialog-message";
|
||||
text.textContent = config.message;
|
||||
body.append(text);
|
||||
}
|
||||
|
||||
if (config.body) {
|
||||
const nodes = Array.isArray(config.body) ? config.body : [config.body];
|
||||
nodes.forEach((node) => {
|
||||
if (node instanceof Node) {
|
||||
body.append(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
card.append(head, body);
|
||||
|
||||
let autofocusTarget = null;
|
||||
if (config.actions.length > 0) {
|
||||
const actions = document.createElement("div");
|
||||
actions.className = "warpbox-dialog-actions";
|
||||
config.actions.forEach((action) => {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "button " + (action.kind === "primary" ? "button-primary" : action.kind === "ghost" ? "button-ghost" : "button-outline");
|
||||
button.textContent = action.label || "OK";
|
||||
button.addEventListener("click", () => {
|
||||
if (typeof action.onClick === "function") {
|
||||
action.onClick();
|
||||
}
|
||||
if (action.dismiss !== false) {
|
||||
closeDialog();
|
||||
}
|
||||
});
|
||||
if (action.autofocus) {
|
||||
autofocusTarget = button;
|
||||
}
|
||||
actions.append(button);
|
||||
});
|
||||
card.append(actions);
|
||||
}
|
||||
|
||||
overlay.append(card);
|
||||
document.body.append(overlay);
|
||||
document.documentElement.classList.add("warpbox-dialog-open");
|
||||
window.requestAnimationFrame(() => {
|
||||
overlay.classList.add("is-visible");
|
||||
(autofocusTarget || card).focus();
|
||||
});
|
||||
|
||||
function handleKeydown(event) {
|
||||
if (event.key === "Escape") {
|
||||
if (config.dismissible) {
|
||||
event.preventDefault();
|
||||
closeDialog();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (event.key !== "Tab") {
|
||||
return;
|
||||
}
|
||||
const focusable = focusableElements(card);
|
||||
if (focusable.length === 0) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
const first = focusable[0];
|
||||
const last = focusable[focusable.length - 1];
|
||||
if (event.shiftKey && document.activeElement === first) {
|
||||
event.preventDefault();
|
||||
last.focus();
|
||||
} else if (!event.shiftKey && document.activeElement === last) {
|
||||
event.preventDefault();
|
||||
first.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function handleOverlayClick(event) {
|
||||
if (config.dismissible && event.target === overlay) {
|
||||
closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", handleKeydown, true);
|
||||
overlay.addEventListener("click", handleOverlayClick);
|
||||
|
||||
let closed = false;
|
||||
function closeDialog() {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
document.removeEventListener("keydown", handleKeydown, true);
|
||||
overlay.removeEventListener("click", handleOverlayClick);
|
||||
overlay.classList.remove("is-visible");
|
||||
document.documentElement.classList.remove("warpbox-dialog-open");
|
||||
window.setTimeout(() => overlay.remove(), 180);
|
||||
if (previouslyFocused && typeof previouslyFocused.focus === "function") {
|
||||
previouslyFocused.focus();
|
||||
}
|
||||
if (config.onClose) {
|
||||
config.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
element: overlay,
|
||||
close: closeDialog,
|
||||
};
|
||||
}
|
||||
|
||||
window.Warpbox.dialog = dialog;
|
||||
|
||||
window.Warpbox.alertDialog = function alertDialog(message, options) {
|
||||
const config = (typeof options === "object" && options) || {};
|
||||
return new Promise((resolve) => {
|
||||
dialog({
|
||||
...config,
|
||||
message: typeof message === "string" ? message : config.message,
|
||||
actions: [{ label: config.okLabel || "OK", kind: "primary", autofocus: true }],
|
||||
onClose: () => {
|
||||
if (typeof config.onClose === "function") {
|
||||
config.onClose();
|
||||
}
|
||||
resolve();
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.Warpbox.confirmDialog = function confirmDialog(message, options) {
|
||||
const config = (typeof options === "object" && options) || {};
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
function settle(value) {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
resolve(value);
|
||||
}
|
||||
dialog({
|
||||
...config,
|
||||
message: typeof message === "string" ? message : config.message,
|
||||
actions: [
|
||||
{ label: config.cancelLabel || "Cancel", kind: "outline", autofocus: true, onClick: () => settle(false) },
|
||||
{ label: config.confirmLabel || "Confirm", kind: "primary", onClick: () => settle(true) },
|
||||
],
|
||||
onClose: () => {
|
||||
if (typeof config.onClose === "function") {
|
||||
config.onClose();
|
||||
}
|
||||
settle(false);
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
window.Warpbox.promptDialog = function promptDialog(message, options) {
|
||||
const config = (typeof options === "object" && options) || {};
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
function settle(value) {
|
||||
if (settled) {
|
||||
return;
|
||||
}
|
||||
settled = true;
|
||||
resolve(value);
|
||||
}
|
||||
|
||||
const field = document.createElement("input");
|
||||
field.type = config.inputType || "text";
|
||||
field.className = "warpbox-dialog-field";
|
||||
if (config.placeholder) {
|
||||
field.placeholder = config.placeholder;
|
||||
}
|
||||
if (typeof config.value === "string") {
|
||||
field.value = config.value;
|
||||
}
|
||||
|
||||
let controller = null;
|
||||
field.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter") {
|
||||
event.preventDefault();
|
||||
settle(field.value);
|
||||
if (controller) {
|
||||
controller.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
controller = dialog({
|
||||
...config,
|
||||
message: typeof message === "string" ? message : config.message,
|
||||
body: field,
|
||||
actions: [
|
||||
{ label: config.cancelLabel || "Cancel", kind: "outline", onClick: () => settle(null) },
|
||||
{ label: config.okLabel || "OK", kind: "primary", onClick: () => settle(field.value) },
|
||||
],
|
||||
onClose: () => {
|
||||
if (typeof config.onClose === "function") {
|
||||
config.onClose();
|
||||
}
|
||||
settle(null);
|
||||
},
|
||||
});
|
||||
|
||||
window.requestAnimationFrame(() => field.focus());
|
||||
});
|
||||
};
|
||||
})();
|
||||
@@ -17,6 +17,7 @@
|
||||
const RESUMABLE_SESSIONS_KEY = "warpbox-resumable-sessions";
|
||||
const SHARE_CACHE = "warpbox-share-target-v1";
|
||||
const SHARE_LATEST_KEY = "/__warpbox_share_target__/latest";
|
||||
const CELLULAR_WARNING_THRESHOLD_BYTES = 200 * 1024 * 1024;
|
||||
|
||||
if (!form || !dropZone || !fileInput) {
|
||||
return;
|
||||
@@ -106,6 +107,12 @@
|
||||
if (!validateSelectedFilesWithinLimit(selectedFiles)) {
|
||||
return;
|
||||
}
|
||||
if (isSlowOrMeteredConnection() && totalSelectedBytes(selectedFiles) >= CELLULAR_WARNING_THRESHOLD_BYTES) {
|
||||
const proceed = await confirmCellularUpload(selectedFiles);
|
||||
if (!proceed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const submit = form.querySelector("button[type='submit']");
|
||||
const formData = uploadFormData();
|
||||
@@ -228,6 +235,56 @@
|
||||
}
|
||||
}
|
||||
|
||||
function isSlowOrMeteredConnection() {
|
||||
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
||||
if (!connection) {
|
||||
return false;
|
||||
}
|
||||
if (connection.saveData === true) {
|
||||
return true;
|
||||
}
|
||||
return ["slow-2g", "2g", "3g"].includes(connection.effectiveType);
|
||||
}
|
||||
|
||||
function totalSelectedBytes(files) {
|
||||
return files.reduce((sum, file) => sum + file.size, 0);
|
||||
}
|
||||
|
||||
function confirmCellularUpload(files) {
|
||||
const list = document.createElement("div");
|
||||
list.className = "dialog-file-list";
|
||||
files.forEach((file) => {
|
||||
const icon = document.createElement("span");
|
||||
icon.className = "svg-icon svg-icon-document dialog-file-icon";
|
||||
icon.setAttribute("aria-hidden", "true");
|
||||
|
||||
const name = document.createElement("span");
|
||||
name.className = "dialog-file-name";
|
||||
name.textContent = file.name;
|
||||
name.title = file.name;
|
||||
|
||||
const size = document.createElement("span");
|
||||
size.className = "dialog-file-size";
|
||||
size.textContent = window.Warpbox.formatBytes(file.size);
|
||||
|
||||
const row = document.createElement("div");
|
||||
row.className = "dialog-file-row";
|
||||
row.append(icon, name, size);
|
||||
list.append(row);
|
||||
});
|
||||
|
||||
const totalLabel = window.Warpbox.formatBytes(totalSelectedBytes(files));
|
||||
const message = `You're on a slow or metered connection. You're about to upload ${files.length} file${files.length === 1 ? "" : "s"} (${totalLabel} total) — this could take a while or use up your data plan.`;
|
||||
|
||||
return window.Warpbox.confirmDialog(message, {
|
||||
title: "Slow connection detected",
|
||||
variant: "warning",
|
||||
body: list,
|
||||
confirmLabel: "Upload anyway",
|
||||
cancelLabel: "Cancel",
|
||||
});
|
||||
}
|
||||
|
||||
function isShareTargetLaunch() {
|
||||
const params = new URLSearchParams(window.location.search || "");
|
||||
return params.has("share-target");
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
|
||||
<script src="/static/js/05-theme.js?version={{.AppVersion}}"></script>
|
||||
<link rel="stylesheet" href="/static/css/00-base.css?version={{.AppVersion}}">
|
||||
<link rel="stylesheet" href="/static/css/04-dialogs.css?version={{.AppVersion}}">
|
||||
<link rel="stylesheet" href="/static/css/10-layout.css?version={{.AppVersion}}">
|
||||
<link rel="stylesheet" href="/static/css/15-revamp.css?version={{.AppVersion}}">
|
||||
<link rel="stylesheet" href="/static/css/16-retro.css?version={{.AppVersion}}">
|
||||
@@ -70,6 +71,7 @@
|
||||
<script defer src="/static/js/00-utils.js?version={{.AppVersion}}"></script>
|
||||
<script defer src="/static/js/02-pwa.js?version={{.AppVersion}}"></script>
|
||||
<script defer src="/static/js/03-popups.js?version={{.AppVersion}}"></script>
|
||||
<script defer src="/static/js/04-dialogs.js?version={{.AppVersion}}"></script>
|
||||
<script defer src="/static/js/10-file-browser.js?version={{.AppVersion}}"></script>
|
||||
<script defer src="/static/js/12-reactions.js?version={{.AppVersion}}"></script>
|
||||
<script defer src="/static/js/13-share.js?version={{.AppVersion}}"></script>
|
||||
|
||||
Reference in New Issue
Block a user