Desktop: Add File > Save As… (#3034)

* Make file name and document name identical

* Add save as action

* Fix test errors

* Add missing save as action

* Desktop fix drop file open document file message

* Address review comments

* Replace file save suffix with file extension

* Add comment specifying that the upload function takes a html input accept string

* Fix remove file extension in web

* Use let

* Don't show save as menu entry in web

* Don't add SaveDocumentAs in web

* Remove file extension on all open document file calls

---------

Co-authored-by: Dennis Kobert <dennis@kobert.dev>
This commit is contained in:
Timon
2025-08-20 10:09:01 +00:00
committed by GitHub
co-authored by Dennis Kobert
parent 7c30f6168b
commit e70862b399
19 changed files with 152 additions and 73 deletions
@@ -151,9 +151,11 @@
return;
}
if (file.name.endsWith(".graphite")) {
const graphiteFileSuffix = "." + editor.handle.fileExtension();
if (file.name.endsWith(graphiteFileSuffix)) {
const content = await file.text();
editor.handle.openDocumentFile(file.name, content);
const documentName = file.name.slice(0, -graphiteFileSuffix.length);
editor.handle.openDocumentFile(documentName, content);
return;
}
});
+4 -2
View File
@@ -438,9 +438,11 @@
}
// When we eventually have sub-documents, this should be changed to import the document instead of opening it in a separate tab
if (file.name.endsWith(".graphite")) {
const graphiteFileSuffix = "." + editor.handle.fileExtension();
if (file.name.endsWith(graphiteFileSuffix)) {
const content = await file.text();
editor.handle.openDocumentFile(file.name, content);
const documentName = file.name.slice(0, -graphiteFileSuffix.length);
editor.handle.openDocumentFile(documentName, content);
return;
}
});
@@ -83,9 +83,11 @@
return;
}
if (file.name.endsWith(".graphite")) {
const graphiteFileSuffix = "." + editor.handle.fileExtension();
if (file.name.endsWith(graphiteFileSuffix)) {
const content = await file.text();
editor.handle.openDocumentFile(file.name, content);
const documentName = file.name.slice(0, -graphiteFileSuffix.length);
editor.handle.openDocumentFile(documentName, content);
return;
}
});
+5 -2
View File
@@ -334,8 +334,11 @@ export function createInputManager(editor: Editor, dialog: DialogState, portfoli
editor.handle.pasteImage(file.name, new Uint8Array(imageData.data), imageData.width, imageData.height);
}
if (file.name.endsWith(".graphite")) {
editor.handle.openDocumentFile(file.name, await file.text());
const graphiteFileSuffix = "." + editor.handle.fileExtension();
if (file.name.endsWith(graphiteFileSuffix)) {
const content = await file.text();
const documentName = file.name.slice(0, -graphiteFileSuffix.length);
editor.handle.openDocumentFile(documentName, content);
}
});
}
+14 -5
View File
@@ -62,9 +62,16 @@ export function createPortfolioState(editor: Editor) {
}
});
editor.subscriptions.subscribeJsMessage(TriggerOpenDocument, async () => {
const extension = editor.handle.fileSaveSuffix();
const data = await upload(extension, "text");
editor.handle.openDocumentFile(data.filename, data.content);
const suffix = "." + editor.handle.fileExtension();
const data = await upload(suffix, "text");
// Use filename as document name, removing the extension if it exists
let documentName = data.filename;
if (documentName.endsWith(suffix)) {
documentName = documentName.slice(0, -suffix.length);
}
editor.handle.openDocumentFile(documentName, data.content);
});
editor.subscriptions.subscribeJsMessage(TriggerImport, async () => {
const data = await upload("image/*", "both");
@@ -76,8 +83,10 @@ export function createPortfolioState(editor: Editor) {
}
// In case the user accidentally uploads a Graphite file, open it instead of failing to import it
if (data.filename.endsWith(".graphite")) {
editor.handle.openDocumentFile(data.filename, data.content.text);
const graphiteFileSuffix = "." + editor.handle.fileExtension();
if (data.filename.endsWith(graphiteFileSuffix)) {
const documentName = data.filename.slice(0, -graphiteFileSuffix.length);
editor.handle.openDocumentFile(documentName, data.content.text);
return;
}
+3 -2
View File
@@ -22,11 +22,12 @@ export function downloadFile(filename: string, content: ArrayBuffer) {
downloadFileBlob(filename, blob);
}
export async function upload<T extends "text" | "data" | "both">(acceptedExtensions: string, textOrData: T): Promise<UploadResult<T>> {
// See https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/file#accept for the `accept` string format
export async function upload<T extends "text" | "data" | "both">(accept: string, textOrData: T): Promise<UploadResult<T>> {
return new Promise<UploadResult<T>>((resolve, _) => {
const element = document.createElement("input");
element.type = "file";
element.accept = acceptedExtensions;
element.accept = accept;
element.addEventListener(
"change",