diff --git a/apps/client/src/services/open.ts b/apps/client/src/services/open.ts
index 0bcda805e..783fcee48 100644
--- a/apps/client/src/services/open.ts
+++ b/apps/client/src/services/open.ts
@@ -126,7 +126,7 @@ function downloadRevision(noteId: string, revisionId: string) {
/**
* @param url - should be without initial slash!!!
*/
-function getUrlForDownload(url: string) {
+export function getUrlForDownload(url: string) {
if (utils.isElectron()) {
// electron needs absolute URL, so we extract current host, port, protocol
return `${getHost()}/${url}`;
diff --git a/apps/client/src/widgets/NoteDetail.tsx b/apps/client/src/widgets/NoteDetail.tsx
index 8c70ea5cd..d0eb6530a 100644
--- a/apps/client/src/widgets/NoteDetail.tsx
+++ b/apps/client/src/widgets/NoteDetail.tsx
@@ -13,6 +13,7 @@ import Book from "./type_widgets/Book";
import ContentWidget from "./type_widgets/ContentWidget";
import WebView from "./type_widgets/WebView";
import "./NoteDetail.css";
+import File from "./type_widgets/File";
/**
* A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one,
@@ -68,6 +69,7 @@ function getCorrespondingWidget(noteType: ExtendedNoteType | undefined, props: T
case "book": return
case "contentWidget": return
case "webView": return
+ case "file": return
default: break;
}
}
diff --git a/apps/client/src/widgets/type_widgets/File.css b/apps/client/src/widgets/type_widgets/File.css
new file mode 100644
index 000000000..d7f7035c0
--- /dev/null
+++ b/apps/client/src/widgets/type_widgets/File.css
@@ -0,0 +1,41 @@
+.type-file .note-detail {
+ height: 100%;
+}
+
+.note-detail-file {
+ padding: 10px;
+ height: 100%;
+}
+
+.note-split.full-content-width .note-detail-file {
+ padding: 0;
+}
+
+.note-detail.full-height .note-detail-file[data-preview-type="pdf"],
+.note-detail.full-height .note-detail-file[data-preview-type="video"] {
+ overflow: hidden;
+}
+
+.file-preview-content {
+ background-color: var(--accented-background-color);
+ padding: 15px;
+ height: 100%;
+ overflow: auto;
+ margin: 10px;
+}
+
+.note-detail-file > .pdf-preview,
+.note-detail-file > .video-preview {
+ width: 100%;
+ height: 100%;
+ flex-grow: 100;
+}
+
+.note-detail-file > .audio-preview {
+ position: absolute;
+ top: 50%;
+ left: 15px;
+ right: 15px;
+ width: calc(100% - 30px);
+ transform: translateY(-50%);
+}
\ No newline at end of file
diff --git a/apps/client/src/widgets/type_widgets/File.tsx b/apps/client/src/widgets/type_widgets/File.tsx
new file mode 100644
index 000000000..b002f54aa
--- /dev/null
+++ b/apps/client/src/widgets/type_widgets/File.tsx
@@ -0,0 +1,86 @@
+import { VNode } from "preact";
+import { useNoteBlob } from "../react/hooks";
+import "./File.css";
+import { TypeWidgetProps } from "./type_widget";
+import FNote from "../../entities/fnote";
+import { getUrlForDownload } from "../../services/open";
+import Alert from "../react/Alert";
+import { t } from "../../services/i18n";
+
+const TEXT_MAX_NUM_CHARS = 5000;
+
+export default function File({ note }: TypeWidgetProps) {
+ const blob = useNoteBlob(note);
+
+ let preview: VNode | null = null;
+ if (blob?.content) {
+ preview =
+ } else if (note.mime === "application/pdf") {
+ preview =
+ } else if (note.mime.startsWith("video/")) {
+ preview =
+ } else if (note.mime.startsWith("audio/")) {
+ preview =
+ } else {
+ preview =
+ }
+
+ return (
+
+ {preview}
+
+ );
+}
+
+function TextPreview({ content }: { content: string }) {
+ const trimmedContent = content.substring(0, TEXT_MAX_NUM_CHARS);
+ const isTooLarge = trimmedContent.length !== content.length;
+
+ return (
+ <>
+ {isTooLarge && (
+
+ {t("file.too_big", { maxNumChars: TEXT_MAX_NUM_CHARS })}
+
+ )}
+ {trimmedContent}
+ >
+ )
+}
+
+function PdfPreview({ note }: { note: FNote }) {
+ return (
+
+ );
+}
+
+function VideoPreview({ note }: { note: FNote }) {
+ return (
+
+ )
+}
+
+function AudioPreview({ note }: { note: FNote }) {
+ return (
+
+ )
+}
+
+function NoPreview() {
+ return (
+
+ {t("file.file_preview_not_available")}
+
+ );
+}
diff --git a/apps/client/src/widgets/type_widgets_old/file.ts b/apps/client/src/widgets/type_widgets_old/file.ts
deleted file mode 100644
index ccf581471..000000000
--- a/apps/client/src/widgets/type_widgets_old/file.ts
+++ /dev/null
@@ -1,138 +0,0 @@
-import openService from "../../services/open.js";
-import TypeWidget from "./type_widget.js";
-import { t } from "../../services/i18n.js";
-import type { EventData } from "../../components/app_context.js";
-import type FNote from "../../entities/fnote.js";
-
-const TEXT_MAX_NUM_CHARS = 5000;
-
-const TPL = /*html*/`
-
-
-
-
- ${t("file.too_big", { maxNumChars: TEXT_MAX_NUM_CHARS })}
-
-
-
-
-
- ${t("file.file_preview_not_available")}
-
-
-
-
-
-
-
-
`;
-
-export default class FileTypeWidget extends TypeWidget {
-
- private $previewContent!: JQuery;
- private $previewNotAvailable!: JQuery;
- private $previewTooBig!: JQuery;
- private $pdfPreview!: JQuery;
- private $videoPreview!: JQuery;
- private $audioPreview!: JQuery;
-
- static getType() {
- return "file";
- }
-
- doRender() {
- this.$widget = $(TPL);
- this.$previewContent = this.$widget.find(".file-preview-content");
- this.$previewNotAvailable = this.$widget.find(".file-preview-not-available");
- this.$previewTooBig = this.$widget.find(".file-preview-too-big");
- this.$pdfPreview = this.$widget.find(".pdf-preview");
- this.$videoPreview = this.$widget.find(".video-preview");
- this.$audioPreview = this.$widget.find(".audio-preview");
-
- super.doRender();
- }
-
- async doRefresh(note: FNote) {
- this.$widget.show();
-
- const blob = await this.note?.getBlob();
-
- this.$previewContent.empty().hide();
- this.$pdfPreview.attr("src", "").empty().hide();
- this.$previewNotAvailable.hide();
- this.$previewTooBig.addClass("hidden-ext");
- this.$videoPreview.hide();
- this.$audioPreview.hide();
-
- let previewType: string;
-
- if (blob?.content) {
- this.$previewContent.show().scrollTop(0);
- const trimmedContent = blob.content.substring(0, TEXT_MAX_NUM_CHARS);
- if (trimmedContent.length !== blob.content.length) {
- this.$previewTooBig.removeClass("hidden-ext");
- }
- this.$previewContent.text(trimmedContent);
- previewType = "text";
- } else if (note.mime === "application/pdf") {
- this.$pdfPreview.show().attr("src", openService.getUrlForDownload(`api/notes/${this.noteId}/open`));
- previewType = "pdf";
- } else if (note.mime.startsWith("video/")) {
- this.$videoPreview
- .show()
- .attr("src", openService.getUrlForDownload(`api/notes/${this.noteId}/open-partial`))
- .attr("type", this.note?.mime ?? "")
- .css("width", this.$widget.width() ?? 0);
- previewType = "video";
- } else if (note.mime.startsWith("audio/")) {
- this.$audioPreview
- .show()
- .attr("src", openService.getUrlForDownload(`api/notes/${this.noteId}/open-partial`))
- .attr("type", this.note?.mime ?? "")
- .css("width", this.$widget.width() ?? 0);
- previewType = "audio";
- } else {
- this.$previewNotAvailable.show();
- previewType = "not-available";
- }
-
- this.$widget.attr("data-preview-type", previewType ?? "");
- }
-
- async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) {
- if (loadResults.isNoteReloaded(this.noteId)) {
- this.refresh();
- }
- }
-}