Исправлены create UI, PWA-иконки, tooltip A2HS и breakpoint админ-меню 1015px.
devops-tools/wrapped/wrapped-build/pipeline/head This commit looks good
devops-tools/wrapped/wrapped-deploy/pipeline/head This commit looks good

This commit is contained in:
Sergey Antropoff
2026-07-29 21:03:51 +03:00
parent 54749e5e12
commit eef8c4ec57
9 changed files with 80 additions and 106 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

+35 -37
View File
@@ -178,7 +178,8 @@ html[data-theme="light"] body {
50% { filter: saturate(1.2); transform: scale(1.04); }
}
.top-actions { display: flex; gap: 0.5rem; }
.top-actions { display: flex; gap: 0.5rem; overflow: visible; position: relative; z-index: 5; }
.topbar { overflow: visible; }
.icon-btn {
min-width: 42px;
@@ -271,15 +272,22 @@ html[data-theme="dark"] .icon-btn:hover {
overflow: visible;
}
#install-app .ui-tooltip {
top: calc(100% + 0.5rem);
bottom: auto;
left: 50%;
right: auto;
transform: translateX(-50%) translateY(4px);
transform: translateX(-50%) translateY(-4px);
white-space: nowrap;
}
#install-app .ui-tooltip::after {
top: auto;
bottom: 100%;
left: 50%;
right: auto;
transform: translateX(-50%);
border: 5px solid transparent;
border-bottom-color: var(--line);
border-top-color: transparent;
}
#install-app:hover .ui-tooltip,
#install-app:focus-visible .ui-tooltip {
@@ -287,6 +295,25 @@ html[data-theme="dark"] .icon-btn:hover {
visibility: visible;
transform: translateX(-50%);
}
.datetime-split {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.45rem;
}
.datetime-split input {
min-width: 0;
}
.field-label {
display: block;
margin-bottom: 0.35rem;
font-weight: 600;
font-size: 0.88rem;
}
@media (max-width: 720px) {
.datetime-split {
grid-template-columns: 1fr;
}
}
.composer, .success-panel, .result-panel {
display: grid;
@@ -834,51 +861,22 @@ body.busy-open { overflow: hidden; }
}
.file-list { list-style: none; padding: 0; margin: 0; display: grid; gap: 0.4rem; }
.file-list li,
.file-list-item {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(5.5rem, 9rem) auto;
gap: 0.45rem;
.file-list li {
display: flex;
justify-content: space-between;
gap: 0.75rem;
align-items: center;
padding: 0.55rem 0.75rem;
border: 1px solid var(--line);
border-radius: 10px;
font-size: 0.9rem;
}
.file-label-input {
width: 100%;
min-width: 0;
padding: 0.35rem 0.5rem;
font-size: 0.82rem;
}
.file-list button {
border: 0;
background: transparent;
color: var(--danger);
cursor: pointer;
}
.template-chips {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
margin: 0 0 0.35rem;
}
.template-chip {
border: 1px solid var(--line);
background: transparent;
color: var(--muted);
border-radius: 999px;
padding: 0.3rem 0.7rem;
font-size: 0.78rem;
font-weight: 600;
cursor: pointer;
}
.template-chip:hover,
.template-chip:focus-visible {
color: var(--accent-2);
border-color: rgba(75, 134, 240, 0.4);
outline: none;
}
.success-meta-badges {
display: flex;
flex-wrap: wrap;
@@ -1519,7 +1517,7 @@ html[data-theme="light"] .admin-nav {
box-shadow: 0 8px 24px rgba(20, 40, 70, 0.08);
}
@media (max-width: 860px) {
@media (max-width: 1015px) {
.admin-top {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
@@ -1617,7 +1615,7 @@ html[data-theme="light"] .admin-nav {
}
}
@media (min-width: 861px) {
@media (min-width: 1016px) {
.admin-top > .brand {
flex: 0 0 auto;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

+1 -1
View File
@@ -4,7 +4,7 @@
const nav = document.getElementById("admin-nav");
if (!top || !toggle || !nav) return;
const mq = window.matchMedia("(max-width: 860px)");
const mq = window.matchMedia("(max-width: 1015px)");
const syncLabel = () => {
const open = top.classList.contains("is-nav-open");
+17 -57
View File
@@ -1,14 +1,12 @@
(() => {
const t = (k, vars) => window.WrappedI18n.t(k, vars);
/** @type {{ file: File, label: string }[]} */
/** @type {File[]} */
const files = [];
let settings = null;
let captchaWidgetId = null;
const els = {
text: document.getElementById("payload-text"),
textLabel: document.getElementById("text-label"),
templateChips: document.getElementById("template-chips"),
lang: document.getElementById("text-language"),
langChip: document.getElementById("lang-chip"),
langChipText: document.getElementById("lang-chip-text"),
@@ -18,7 +16,8 @@
editor: document.getElementById("code-editor"),
ttl: document.getElementById("ttl-seconds"),
maxOpens: document.getElementById("max-opens"),
availableFrom: document.getElementById("available-from"),
availableFromDate: document.getElementById("available-from-date"),
availableFromTime: document.getElementById("available-from-time"),
password: document.getElementById("password"),
generatePassword: document.getElementById("generate-password"),
dropzone: document.getElementById("dropzone"),
@@ -114,7 +113,7 @@
function estimatePayloadBytes() {
const textBytes = new TextEncoder().encode(els.text?.value || "").length;
const fileBytes = files.reduce((sum, entry) => sum + (Number(entry.file.size) || 0), 0);
const fileBytes = files.reduce((sum, f) => sum + (Number(f.size) || 0), 0);
return textBytes + fileBytes;
}
@@ -142,21 +141,9 @@
function renderFiles() {
els.fileList.innerHTML = "";
files.forEach((entry, idx) => {
const f = entry.file;
files.forEach((f, idx) => {
const li = document.createElement("li");
li.className = "file-list-item";
const meta = document.createElement("span");
meta.innerHTML = `${escapeHtml(f.name)} <small>(${escapeHtml(f.type || "file")} · ${window.WrappedUI.formatBytes(f.size)})</small>`;
const labelInput = document.createElement("input");
labelInput.type = "text";
labelInput.className = "file-label-input";
labelInput.maxLength = 120;
labelInput.placeholder = t("create.itemLabelPlaceholder");
labelInput.value = entry.label || "";
labelInput.addEventListener("input", () => {
entry.label = labelInput.value;
});
li.innerHTML = `<span>${escapeHtml(f.name)} <small>(${escapeHtml(f.type || "file")} · ${window.WrappedUI.formatBytes(f.size)})</small></span>`;
const btn = document.createElement("button");
btn.type = "button";
btn.textContent = "×";
@@ -164,8 +151,6 @@
files.splice(idx, 1);
renderFiles();
});
li.appendChild(meta);
li.appendChild(labelInput);
li.appendChild(btn);
els.fileList.appendChild(li);
});
@@ -186,7 +171,7 @@
showError(t("create.mimeDenied") + `: ${f.type || f.name}`);
continue;
}
files.push({ file: f, label: "" });
files.push(f);
}
renderFiles();
}
@@ -245,31 +230,11 @@
.join("");
}
function renderTemplates() {
if (!els.templateChips) return;
els.templateChips.innerHTML = "";
const keys = [
{ id: "access", key: "create.tpl.access" },
{ id: "code", key: "create.tpl.code" },
{ id: "fileNote", key: "create.tpl.fileNote" },
];
for (const item of keys) {
const btn = document.createElement("button");
btn.type = "button";
btn.className = "template-chip";
btn.textContent = t(`create.tpl.${item.id}.name`);
btn.addEventListener("click", () => {
els.text.value = t(`create.tpl.${item.id}.body`);
refreshHighlight();
refreshSizeMeter();
});
els.templateChips.appendChild(btn);
}
}
function datetimeLocalToIso(value) {
if (!value) return null;
const d = new Date(value);
function availableFromToIso() {
const date = (els.availableFromDate?.value || "").trim();
if (!date) return null;
const time = (els.availableFromTime?.value || "").trim() || "00:00";
const d = new Date(`${date}T${time}`);
if (Number.isNaN(d.getTime())) return null;
return d.toISOString();
}
@@ -351,7 +316,6 @@
settings = await resp.json();
fillTtl();
fillMaxOpens();
renderTemplates();
loadCaptcha();
setLanguage("plaintext", { silent: true });
refreshHighlight();
@@ -525,18 +489,15 @@
const items = [];
const contentTypes = [];
if (text.trim()) {
const textItem = {
items.push({
type: "text",
language: els.lang.value,
content: text,
};
const label = (els.textLabel?.value || "").trim();
if (label) textItem.label = label.slice(0, 120);
items.push(textItem);
});
contentTypes.push("text/plain");
}
for (const entry of files) {
const item = await window.WrappedCrypto.fileToItem(entry.file, entry.label);
for (const f of files) {
const item = await window.WrappedCrypto.fileToItem(f);
items.push(item);
contentTypes.push(item.mime);
}
@@ -550,7 +511,7 @@
const password = els.password.value || "";
const maxOpens = Number(els.maxOpens?.value || 1);
const availableFromIso = datetimeLocalToIso(els.availableFrom?.value || "");
const availableFromIso = availableFromToIso();
els.wrapBtn.disabled = true;
window.WrappedUI.showBusy(t("create.working"));
try {
@@ -638,7 +599,6 @@
window.WrappedI18n.onChange(() => {
fillTtl();
fillMaxOpens();
renderTemplates();
refreshExpiresMeta();
refreshSuccessBadges();
refreshSizeMeter();
+21 -3
View File
@@ -8,10 +8,28 @@
"theme_color": "#0b1020",
"icons": [
{
"src": "/static/favicon.svg",
"sizes": "any",
"type": "image/svg+xml",
"src": "/static/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/static/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/static/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
+5 -7
View File
@@ -14,11 +14,6 @@
</div>
<label class="sr-only" for="payload-text" data-i18n="create.text">Text</label>
<div class="template-chips" id="template-chips" role="group" aria-label="Templates"></div>
<div class="field">
<label for="text-label" data-i18n="create.itemLabel">Label (optional)</label>
<input id="text-label" type="text" maxlength="120" data-i18n-placeholder="create.itemLabelPlaceholder" placeholder="e.g. Instructions" />
</div>
<div class="code-editor" id="code-editor">
<pre class="code-highlight" aria-hidden="true"><code id="code-highlight" class="hljs"></code></pre>
<textarea id="payload-text" class="code-input" spellcheck="false" data-i18n-placeholder="create.textPlaceholder" placeholder="Paste secrets, configs, notes…"></textarea>
@@ -43,8 +38,11 @@
</div>
<div class="grid-2">
<div class="field">
<label for="available-from" data-i18n="create.availableFrom">Available from (optional)</label>
<input id="available-from" type="datetime-local" />
<span class="field-label" data-i18n="create.availableFrom">Available from (optional)</span>
<div class="datetime-split">
<input id="available-from-date" type="date" aria-label="Date" />
<input id="available-from-time" type="time" step="60" aria-label="Time" />
</div>
<p class="hint" data-i18n="create.availableFromHint">Empty = available immediately</p>
</div>
<div class="field">