Skip to content

Commit 0234403

Browse files
committed
feat: add copy file content and hash to clipboard
1 parent 54e4857 commit 0234403

File tree

1 file changed

+63
-8
lines changed

1 file changed

+63
-8
lines changed

src/index.php

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,15 @@ function numsize($size, $round = 2)
4343
return round($size / pow(1000, ($i = floor(log($size, 1000)))), $round) . $unit[$i];
4444
}
4545

46+
function safe_utf8(string $input): string
47+
{
48+
// Ensure JSON output is valid UTF-8.
49+
// iconv is commonly available; if it fails, fall back to stripping invalid bytes.
50+
$converted = @iconv('UTF-8', 'UTF-8//IGNORE', $input);
51+
if ($converted !== false) return $converted;
52+
return preg_replace('/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/u', '', $input) ?? '';
53+
}
54+
4655
// fix whitespace in path results in not found errors
4756
$request_uri = rawurldecode(parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH));
4857

@@ -1092,8 +1101,11 @@ function toggletheme() {
10921101
</div>
10931102
</div>
10941103
<div class="modal-footer">
1095-
${{`process.env.API === "true" ? '<a id="file-info-url-api" href="" type="button" class="btn rounded btn-secondary" data-bs-dismiss="modal">API <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-code"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 8l-4 4l4 4" /><path d="M17 8l4 4l-4 4" /><path d="M14 4l-4 16" /></svg></a>' : ''`}}$
1096-
<a id="file-info-url" type="button" class="btn rounded btn-primary">Download <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-download"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" /><path d="M7 11l5 5l5 -5" /><path d="M12 4l0 12" /></svg></a>
1104+
<!-- TODO: add copy button if kind == text -->
1105+
<button id="file-popup-copy" type="button" class="btn rounded btn-secondary" disabled><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-copy"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 7m0 2.667a2.667 2.667 0 0 1 2.667 -2.667h8.666a2.667 2.667 0 0 1 2.667 2.667v8.666a2.667 2.667 0 0 1 -2.667 2.667h-8.666a2.667 2.667 0 0 1 -2.667 -2.667z" /><path d="M4.012 16.737a2.005 2.005 0 0 1 -1.012 -1.737v-10c0 -1.1 .9 -2 2 -2h10c.75 0 1.158 .385 1.5 1" /></svg> Copy Text</button>
1106+
1107+
${{`process.env.API === "true" ? '<a id="file-info-url-api" href="?info" target="_blank" type="button" class="btn rounded btn-secondary"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-code"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M7 8l-4 4l4 4" /><path d="M17 8l4 4l-4 4" /><path d="M14 4l-4 16" /></svg> API</a>' : ''`}}$
1108+
<a id="file-info-url" type="button" class="btn rounded btn-primary"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-download"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2" /><path d="M7 11l5 5l5 -5" /><path d="M12 4l0 12" /></svg> Download</a>
10971109
</div>
10981110
</div>
10991111
</div>
@@ -1102,6 +1114,38 @@ function toggletheme() {
11021114
11031115
<!-- Powered by https://github.com/adrianschubek/dir-browser -->
11041116
<script data-turbo-eval="false">
1117+
const copyTextToClipboard = async (text) => {
1118+
if (typeof text !== 'string' || text.length === 0) return false;
1119+
1120+
// Prefer async clipboard API when available (requires secure context).
1121+
try {
1122+
if (navigator.clipboard && window.isSecureContext) {
1123+
await navigator.clipboard.writeText(text);
1124+
return true;
1125+
}
1126+
} catch (e) {
1127+
// Fall back below.
1128+
}
1129+
1130+
// HTTP / non-secure fallback using execCommand.
1131+
try {
1132+
const textarea = document.createElement('textarea');
1133+
textarea.value = text;
1134+
textarea.setAttribute('readonly', '');
1135+
textarea.style.position = 'fixed';
1136+
textarea.style.top = '-1000px';
1137+
textarea.style.left = '-1000px';
1138+
document.body.appendChild(textarea);
1139+
textarea.select();
1140+
textarea.setSelectionRange(0, textarea.value.length);
1141+
const ok = document.execCommand('copy');
1142+
document.body.removeChild(textarea);
1143+
return ok;
1144+
} catch (e) {
1145+
return false;
1146+
}
1147+
};
1148+
11051149
$[if `process.env.DATE_FORMAT === "relative"`]$
11061150
function getRelativeTimeString(date, lang = navigator.language) {
11071151
const timeMs = typeof date === "number" ? date : date.getTime();
@@ -1133,11 +1177,11 @@ function getRelativeTimeString(date, lang = navigator.language) {
11331177
$[if `process.env.HASH === "true"`]$
11341178
// via api bc otherwise we need to include the hash in the tree itself which is costly
11351179
const getHashViaApi = async (url) => {
1136-
await fetch(url)
1137-
.then(response => response.json())
1138-
.then(data => {
1139-
navigator.clipboard.writeText(data.hash_${{`process.env.HASH_ALGO`}}$);
1140-
});
1180+
const res = await fetch(url);
1181+
if (!res.ok) throw new Error('Hash request failed');
1182+
const data = await res.json();
1183+
const hash = data.hash_${{`process.env.HASH_ALGO`}}$;
1184+
await copyTextToClipboard(String(hash ?? ''));
11411185
}
11421186
$[end]$
11431187
@@ -1542,7 +1586,18 @@ function getRelativeTimeString(date, lang = navigator.language) {
15421586
updateMultiselect((localStorage.getItem("multiSelectMode") ?? false) === "true");
15431587
$[end]$
15441588
1545-
$[if `process.env.LAYOUT === "popup" || process.env.LAYOUT === "full"`]$
1589+
$[if `process.env.LAYOUT === "popup"`]$
1590+
(() => {
1591+
const copyBtn = document.querySelector('#file-popup-copy');
1592+
if (copyBtn && copyBtn.dataset.dbBoundClick !== '1') {
1593+
copyBtn.dataset.dbBoundClick = '1';
1594+
copyBtn.addEventListener('click', async (e) => {
1595+
e.preventDefault();
1596+
await copyPreviewToClipboard();
1597+
});
1598+
}
1599+
})();
1600+
15461601
document.querySelectorAll('.db-file').forEach((item) => {
15471602
// skip folders
15481603
if (item.getAttribute('data-file-isdir') === '1') {

0 commit comments

Comments
 (0)