1
0
mirror of https://github.com/coalaura/whiskr.git synced 2025-09-08 17:06:42 +00:00

multi-file select

This commit is contained in:
Laura
2025-08-26 00:48:46 +02:00
parent 5e10c86697
commit b319dce942
2 changed files with 75 additions and 47 deletions

View File

@@ -61,7 +61,7 @@
const notification = make("div", "notification", "off-screen"); const notification = make("div", "notification", "off-screen");
notification.textContent = msg; notification.textContent = msg instanceof Error ? msg.message : msg;
$notifications.appendChild(notification); $notifications.appendChild(notification);
@@ -84,8 +84,6 @@
notification.remove(); notification.remove();
} }
window.notify = notify;
function updateTitle() { function updateTitle() {
const title = chatTitle || (messages.length ? "New Chat" : ""); const title = chatTitle || (messages.length ? "New Chat" : "");
@@ -1112,7 +1110,7 @@
return; return;
} }
notify(err.message); notify(err);
} }
titleController = null; titleController = null;
@@ -1329,6 +1327,8 @@
attachments.splice(index, 1); attachments.splice(index, 1);
storeValue("attachments", attachments);
el.remove(); el.remove();
$attachments.classList.toggle("has-files", !!attachments.length); $attachments.classList.toggle("has-files", !!attachments.length);
@@ -1506,34 +1506,34 @@
}); });
$upload.addEventListener("click", async () => { $upload.addEventListener("click", async () => {
const file = await selectFile( const files = await selectFile(
// the ultimate list // the ultimate list
".adoc,.bash,.bashrc,.bat,.c,.cc,.cfg,.cjs,.cmd,.conf,.cpp,.cs,.css,.csv,.cxx,.dockerfile,.dockerignore,.editorconfig,.env,.fish,.fs,.fsx,.gitattributes,.gitignore,.go,.gradle,.groovy,.h,.hh,.hpp,.htm,.html,.ini,.ipynb,.java,.jl,.js,.json,.jsonc,.jsx,.kt,.kts,.less,.log,.lua,.m,.makefile,.markdown,.md,.mjs,.mk,.mm,.php,.phtml,.pl,.pm,.profile,.properties,.ps1,.psql,.py,.pyw,.r,.rb,.rs,.rst,.sass,.scala,.scss,.sh,.sql,.svelte,.swift,.t,.toml,.ts,.tsv,.tsx,.txt,.vb,.vue,.xhtml,.xml,.xsd,.xsl,.xslt,.yaml,.yml,.zig,.zsh", ".adoc,.bash,.bashrc,.bat,.c,.cc,.cfg,.cjs,.cmd,.conf,.cpp,.cs,.css,.csv,.cxx,.dockerfile,.dockerignore,.editorconfig,.env,.fish,.fs,.fsx,.gitattributes,.gitignore,.go,.gradle,.groovy,.h,.hh,.hpp,.htm,.html,.ini,.ipynb,.java,.jl,.js,.json,.jsonc,.jsx,.kt,.kts,.less,.log,.lua,.m,.makefile,.markdown,.md,.mjs,.mk,.mm,.php,.phtml,.pl,.pm,.profile,.properties,.ps1,.psql,.py,.pyw,.r,.rb,.rs,.rst,.sass,.scala,.scss,.sh,.sql,.svelte,.swift,.t,.toml,.ts,.tsv,.tsx,.txt,.vb,.vue,.xhtml,.xml,.xsd,.xsl,.xslt,.yaml,.yml,.zig,.zsh",
false true,
file => {
if (!file.name) {
file.name = "unknown.txt";
} else if (file.name.length > 512) {
throw new Error("File name too long (max 512 characters)");
}
if (typeof file.content !== "string") {
throw new Error("File is not a text file");
} else if (!file.content) {
throw new Error("File is empty");
} else if (file.content.length > 4 * 1024 * 1024) {
throw new Error("File is too big (max 4MB)");
}
},
notify
); );
if (!file) { if (!files.length) {
return; return;
} }
try { for (const file of files) {
if (!file.name) {
file.name = "unknown.txt";
} else if (file.name.length > 512) {
throw new Error("File name too long (max 512 characters)");
}
if (typeof file.content !== "string") {
throw new Error("File is not a text file");
} else if (!file.content) {
throw new Error("File is empty");
} else if (file.content.length > 4 * 1024 * 1024) {
throw new Error("File is too big (max 4MB)");
}
pushAttachment(file); pushAttachment(file);
} catch (err) {
notify(err.message);
} }
}); });
@@ -1579,7 +1579,14 @@
return; return;
} }
const file = await selectFile("application/json", true), const file = await selectFile(
"application/json",
false,
file => {
file.content = JSON.parse(file.content);
},
notify
),
data = file?.content; data = file?.content;
if (!data) { if (!data) {

View File

@@ -156,46 +156,67 @@ function lines(text) {
return count + 1; return count + 1;
} }
function selectFile(accept, asJson = false) { function readFile(file, handler, onError = false) {
return new Promise(resolve => {
const reader = new FileReader();
reader.onload = () => {
try {
const result = {
name: file.name,
content: reader.result,
};
handler(result);
resolve(result);
} catch (err) {
onError?.(`${file.name}: ${err.message}`);
resolve(false);
}
};
reader.onerror = () => resolve(false);
reader.readAsText(file);
});
}
function selectFile(accept, multiple, handler, onError = false) {
return new Promise(resolve => { return new Promise(resolve => {
const input = make("input"); const input = make("input");
input.type = "file"; input.type = "file";
input.accept = accept; input.accept = accept;
input.multiple = multiple;
input.onchange = () => { input.onchange = async () => {
const file = input.files[0]; const files = input.files;
if (!file) { if (!files.length) {
resolve(false); resolve(false);
return; return;
} }
const reader = new FileReader(); const results = [];
reader.onload = () => { for (const file of files) {
let content = reader.result; const result = await readFile(file, handler, onError);
if (asJson) { if (result) {
try { results.push(result);
content = JSON.parse(content);
} catch {
resolve(false);
return;
}
} }
}
resolve({ if (!results.length) {
name: file.name, resolve(false);
content: content,
});
};
reader.onerror = () => resolve(false); return;
}
reader.readAsText(file); resolve(multiple ? results : results[0]);
}; };
input.click(); input.click();