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 ( + - - - - -`; - -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(); - } - } -}