Force download for .url/.txt/.png/.qr.txt chips
Browser often opens these files instead of downloading; use blob downloads with embedded text fallbacks and canvas for PNG on file://.
This commit is contained in:
@@ -734,12 +734,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<nav class="files" aria-label="Файлы">
|
<nav class="files" aria-label="Файлы">
|
||||||
<a href="{{ server.dir | e }}/{{ user.name | e }}.url" download>.url</a>
|
<a class="file-download" href="{{ server.dir | e }}/{{ user.name | e }}.url" download="{{ server.name | e }}-{{ user.name | e }}.url" data-filename="{{ server.name | e }}-{{ user.name | e }}.url" data-text="{{ (user.url ~ '\n') | e }}">.url</a>
|
||||||
<a href="{{ server.dir | e }}/{{ user.name | e }}.txt" download>.txt</a>
|
<a class="file-download" href="{{ server.dir | e }}/{{ user.name | e }}.txt" download="{{ server.name | e }}-{{ user.name | e }}.txt" data-filename="{{ server.name | e }}-{{ user.name | e }}.txt" data-text="{{ ('Server: ' ~ server.name ~ '\nDomain: ' ~ server.domain ~ ':' ~ server.port ~ '\nUser: ' ~ user.name ~ '\nPassword: ' ~ user.password ~ '\n\nURL:\n' ~ user.url ~ '\n') | e }}">.txt</a>
|
||||||
{% if user.has_png | default(false) %}
|
{% if user.has_png | default(false) %}
|
||||||
<a href="{{ server.dir | e }}/{{ user.name | e }}.png" download>.png</a>
|
<a class="file-download" href="{{ server.dir | e }}/{{ user.name | e }}.png" download="{{ server.name | e }}-{{ user.name | e }}.png" data-filename="{{ server.name | e }}-{{ user.name | e }}.png">.png</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ server.dir | e }}/{{ user.name | e }}.qr.txt" download>.qr.txt</a>
|
<a class="file-download" href="{{ server.dir | e }}/{{ user.name | e }}.qr.txt" download="{{ server.name | e }}-{{ user.name | e }}.qr.txt" data-filename="{{ server.name | e }}-{{ user.name | e }}.qr.txt" data-text="{{ ('URL: ' ~ user.url ~ '\n') | e }}">.qr.txt</a>
|
||||||
</nav>
|
</nav>
|
||||||
</article>
|
</article>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -936,6 +936,71 @@
|
|||||||
if (e.key === 'Escape' && box.classList.contains('open')) closeLightbox();
|
if (e.key === 'Escape' && box.classList.contains('open')) closeLightbox();
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
function triggerFileDownload(blob, filename) {
|
||||||
|
var url = URL.createObjectURL(blob);
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
a.rel = 'noopener';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
setTimeout(function () { URL.revokeObjectURL(url); }, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pngFromCard(link, filename) {
|
||||||
|
var card = link.closest('.user-card');
|
||||||
|
var qrImg = card && card.querySelector('img[data-lightbox-src], .qr-block img');
|
||||||
|
if (!qrImg) return false;
|
||||||
|
try {
|
||||||
|
if (!qrImg.complete || !qrImg.naturalWidth) return false;
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.width = qrImg.naturalWidth;
|
||||||
|
canvas.height = qrImg.naturalHeight;
|
||||||
|
canvas.getContext('2d').drawImage(qrImg, 0, 0);
|
||||||
|
canvas.toBlob(function (blob) {
|
||||||
|
if (blob) triggerFileDownload(blob, filename);
|
||||||
|
}, 'image/png');
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('a.file-download').forEach(function (link) {
|
||||||
|
link.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
var filename = link.getAttribute('data-filename') || link.getAttribute('download') || 'download';
|
||||||
|
var href = link.getAttribute('href');
|
||||||
|
var fallbackText = link.getAttribute('data-text');
|
||||||
|
var isPng = /\.png$/i.test(filename);
|
||||||
|
|
||||||
|
function fallback() {
|
||||||
|
if (fallbackText !== null) {
|
||||||
|
triggerFileDownload(new Blob([fallbackText], { type: 'text/plain;charset=utf-8' }), filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isPng) pngFromCard(link, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!href) {
|
||||||
|
fallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(href)
|
||||||
|
.then(function (res) {
|
||||||
|
if (!res.ok) throw new Error('fetch failed');
|
||||||
|
return res.blob();
|
||||||
|
})
|
||||||
|
.then(function (blob) { triggerFileDownload(blob, filename); })
|
||||||
|
.catch(fallback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -440,12 +440,12 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<nav class="files" aria-label="Файлы">
|
<nav class="files" aria-label="Файлы">
|
||||||
<a href="{{ user.name | e }}.url" download>.url</a>
|
<a class="file-download" href="{{ user.name | e }}.url" download="{{ hysteria2_output_name | e }}-{{ user.name | e }}.url" data-filename="{{ hysteria2_output_name | e }}-{{ user.name | e }}.url" data-text="{{ (user.url ~ '\n') | e }}">.url</a>
|
||||||
<a href="{{ user.name | e }}.txt" download>.txt</a>
|
<a class="file-download" href="{{ user.name | e }}.txt" download="{{ hysteria2_output_name | e }}-{{ user.name | e }}.txt" data-filename="{{ hysteria2_output_name | e }}-{{ user.name | e }}.txt" data-text="{{ ('Server: ' ~ hysteria2_output_name ~ '\nDomain: ' ~ hysteria2_domain ~ ':' ~ hysteria2_listen_port ~ '\nUser: ' ~ user.name ~ '\nPassword: ' ~ user.password ~ '\n\nURL:\n' ~ user.url ~ '\n') | e }}">.txt</a>
|
||||||
{% if user.has_png | default(false) %}
|
{% if user.has_png | default(false) %}
|
||||||
<a href="{{ user.name | e }}.png" download>.png</a>
|
<a class="file-download" href="{{ user.name | e }}.png" download="{{ hysteria2_output_name | e }}-{{ user.name | e }}.png" data-filename="{{ hysteria2_output_name | e }}-{{ user.name | e }}.png">.png</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{{ user.name | e }}.qr.txt" download>.qr.txt</a>
|
<a class="file-download" href="{{ user.name | e }}.qr.txt" download="{{ hysteria2_output_name | e }}-{{ user.name | e }}.qr.txt" data-filename="{{ hysteria2_output_name | e }}-{{ user.name | e }}.qr.txt" data-text="{{ ('URL: ' ~ user.url ~ '\n') | e }}">.qr.txt</a>
|
||||||
</nav>
|
</nav>
|
||||||
</article>
|
</article>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@@ -613,6 +613,71 @@
|
|||||||
if (e.key === 'Escape' && box.classList.contains('open')) closeLightbox();
|
if (e.key === 'Escape' && box.classList.contains('open')) closeLightbox();
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
(function () {
|
||||||
|
function triggerFileDownload(blob, filename) {
|
||||||
|
var url = URL.createObjectURL(blob);
|
||||||
|
var a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
a.rel = 'noopener';
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
setTimeout(function () { URL.revokeObjectURL(url); }, 1500);
|
||||||
|
}
|
||||||
|
|
||||||
|
function pngFromCard(link, filename) {
|
||||||
|
var card = link.closest('.user-card');
|
||||||
|
var qrImg = card && card.querySelector('img[data-lightbox-src], .qr-block img');
|
||||||
|
if (!qrImg) return false;
|
||||||
|
try {
|
||||||
|
if (!qrImg.complete || !qrImg.naturalWidth) return false;
|
||||||
|
var canvas = document.createElement('canvas');
|
||||||
|
canvas.width = qrImg.naturalWidth;
|
||||||
|
canvas.height = qrImg.naturalHeight;
|
||||||
|
canvas.getContext('2d').drawImage(qrImg, 0, 0);
|
||||||
|
canvas.toBlob(function (blob) {
|
||||||
|
if (blob) triggerFileDownload(blob, filename);
|
||||||
|
}, 'image/png');
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('a.file-download').forEach(function (link) {
|
||||||
|
link.addEventListener('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
var filename = link.getAttribute('data-filename') || link.getAttribute('download') || 'download';
|
||||||
|
var href = link.getAttribute('href');
|
||||||
|
var fallbackText = link.getAttribute('data-text');
|
||||||
|
var isPng = /\.png$/i.test(filename);
|
||||||
|
|
||||||
|
function fallback() {
|
||||||
|
if (fallbackText !== null) {
|
||||||
|
triggerFileDownload(new Blob([fallbackText], { type: 'text/plain;charset=utf-8' }), filename);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isPng) pngFromCard(link, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!href) {
|
||||||
|
fallback();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch(href)
|
||||||
|
.then(function (res) {
|
||||||
|
if (!res.ok) throw new Error('fetch failed');
|
||||||
|
return res.blob();
|
||||||
|
})
|
||||||
|
.then(function (blob) { triggerFileDownload(blob, filename); })
|
||||||
|
.catch(fallback);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user