mirror of
https://github.com/zadam/trilium.git
synced 2026-03-09 21:50:24 +01:00
feat(spreadsheet): basic note list preview using SVG
This commit is contained in:
@@ -54,7 +54,7 @@ export async function getRenderedContent(this: {} | { ctx: string }, entity: FNo
|
||||
await renderText(entity, $renderedContent, options);
|
||||
} else if (type === "code") {
|
||||
await renderCode(entity, $renderedContent);
|
||||
} else if (["image", "canvas", "mindMap"].includes(type)) {
|
||||
} else if (["image", "canvas", "mindMap", "spreadsheet"].includes(type)) {
|
||||
renderImage(entity, $renderedContent, options);
|
||||
} else if (!options.tooltip && ["file", "pdf", "audio", "video"].includes(type)) {
|
||||
await renderFile(entity, type, $renderedContent);
|
||||
|
||||
@@ -89,7 +89,7 @@ async function remove<T>(url: string, componentId?: string) {
|
||||
return await call<T>("DELETE", url, componentId);
|
||||
}
|
||||
|
||||
async function upload(url: string, fileToUpload: File, componentId?: string) {
|
||||
async function upload(url: string, fileToUpload: File, componentId?: string, method = "PUT") {
|
||||
const formData = new FormData();
|
||||
formData.append("upload", fileToUpload);
|
||||
|
||||
@@ -99,7 +99,7 @@ async function upload(url: string, fileToUpload: File, componentId?: string) {
|
||||
"trilium-component-id": componentId
|
||||
} : undefined),
|
||||
data: formData,
|
||||
type: "PUT",
|
||||
type: method,
|
||||
timeout: 60 * 60 * 1000,
|
||||
contentType: false, // NEEDED, DON'T REMOVE THIS
|
||||
processData: false // NEEDED, DON'T REMOVE THIS
|
||||
|
||||
@@ -98,6 +98,7 @@ export interface SavedData {
|
||||
mime: string;
|
||||
content: string;
|
||||
position: number;
|
||||
encoding?: "base64";
|
||||
}[];
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@ import { MutableRef, useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import NoteContext from "../../components/note_context";
|
||||
import FNote from "../../entities/fnote";
|
||||
import { useColorScheme, useEditorSpacedUpdate, useTriliumEvent } from "../react/hooks";
|
||||
import server from "../../services/server";
|
||||
import { SavedData, useColorScheme, useEditorSpacedUpdate, useTriliumEvent } from "../react/hooks";
|
||||
import { TypeWidgetProps } from "./type_widget";
|
||||
|
||||
interface PersistedData {
|
||||
@@ -22,7 +23,7 @@ export default function Spreadsheet({ note, noteContext }: TypeWidgetProps) {
|
||||
|
||||
useInitializeSpreadsheet(containerRef, apiRef);
|
||||
useDarkMode(apiRef);
|
||||
usePersistence(note, noteContext, apiRef);
|
||||
usePersistence(note, noteContext, apiRef, containerRef);
|
||||
|
||||
// Focus the spreadsheet when the note is focused.
|
||||
useTriliumEvent("focusOnDetail", () => {
|
||||
@@ -68,14 +69,14 @@ function useDarkMode(apiRef: MutableRef<FUniver | undefined>) {
|
||||
}, [ colorScheme, apiRef ]);
|
||||
}
|
||||
|
||||
function usePersistence(note: FNote, noteContext: NoteContext | null | undefined, apiRef: MutableRef<FUniver | undefined>) {
|
||||
function usePersistence(note: FNote, noteContext: NoteContext | null | undefined, apiRef: MutableRef<FUniver | undefined>, containerRef: MutableRef<HTMLDivElement | null>) {
|
||||
const changeListener = useRef<IDisposable>(null);
|
||||
|
||||
const spacedUpdate = useEditorSpacedUpdate({
|
||||
noteType: "spreadsheet",
|
||||
note,
|
||||
noteContext,
|
||||
getData() {
|
||||
async getData() {
|
||||
const univerAPI = apiRef.current;
|
||||
if (!univerAPI) return undefined;
|
||||
const workbook = univerAPI.getActiveWorkbook();
|
||||
@@ -84,8 +85,25 @@ function usePersistence(note: FNote, noteContext: NoteContext | null | undefined
|
||||
version: 1,
|
||||
workbook: workbook.save()
|
||||
};
|
||||
|
||||
const attachments: SavedData["attachments"] = [];
|
||||
const canvasEl = containerRef.current?.querySelector<HTMLCanvasElement>("canvas[id]");
|
||||
if (canvasEl) {
|
||||
const dataUrl = canvasEl.toDataURL("image/png");
|
||||
const base64 = dataUrl.split(",")[1];
|
||||
attachments.push({
|
||||
role: "image",
|
||||
title: "spreadsheet-export.png",
|
||||
mime: "image/png",
|
||||
content: base64,
|
||||
position: 0,
|
||||
encoding: "base64"
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
content: JSON.stringify(content)
|
||||
content: JSON.stringify(content),
|
||||
attachments
|
||||
};
|
||||
},
|
||||
onContentChange(newContent) {
|
||||
|
||||
@@ -23,7 +23,7 @@ function returnImageInt(image: BNote | BRevision | null, res: Response) {
|
||||
if (!image) {
|
||||
res.set("Content-Type", "image/png");
|
||||
return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`));
|
||||
} else if (!["image", "canvas", "mermaid", "mindMap"].includes(image.type)) {
|
||||
} else if (!["image", "canvas", "mermaid", "mindMap", "spreadsheet"].includes(image.type)) {
|
||||
return res.sendStatus(400);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ function returnImageInt(image: BNote | BRevision | null, res: Response) {
|
||||
renderSvgAttachment(image, res, "mermaid-export.svg");
|
||||
} else if (image.type === "mindMap") {
|
||||
renderSvgAttachment(image, res, "mindmap-export.svg");
|
||||
} else if (image.type === "spreadsheet") {
|
||||
renderPngAttachment(image, res, "spreadsheet-export.png");
|
||||
} else {
|
||||
res.set("Content-Type", image.mime);
|
||||
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
@@ -60,6 +62,18 @@ export function renderSvgAttachment(image: BNote | BRevision, res: Response, att
|
||||
res.send(svg);
|
||||
}
|
||||
|
||||
export function renderPngAttachment(image: BNote | BRevision, res: Response, attachmentName: string) {
|
||||
const attachment = image.getAttachmentByTitle(attachmentName);
|
||||
|
||||
if (attachment) {
|
||||
res.set("Content-Type", "image/png");
|
||||
res.set("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||
res.send(attachment.getContent());
|
||||
} else {
|
||||
res.sendStatus(404);
|
||||
}
|
||||
}
|
||||
|
||||
function returnAttachedImage(req: Request<{ attachmentId: string }>, res: Response) {
|
||||
const attachment = becca.getAttachment(req.params.attachmentId);
|
||||
|
||||
|
||||
@@ -772,16 +772,20 @@ function updateNoteData(noteId: string, content: string, attachments: Attachment
|
||||
if (attachments?.length > 0) {
|
||||
const existingAttachmentsByTitle = toMap(note.getAttachments(), "title");
|
||||
|
||||
for (const { attachmentId, role, mime, title, position, content } of attachments) {
|
||||
for (const { attachmentId, role, mime, title, position, content, encoding } of attachments) {
|
||||
const decodedContent = encoding === "base64" && typeof content === "string"
|
||||
? Buffer.from(content, "base64")
|
||||
: content;
|
||||
|
||||
const existingAttachment = existingAttachmentsByTitle.get(title);
|
||||
if (attachmentId || !existingAttachment) {
|
||||
note.saveAttachment({ attachmentId, role, mime, title, content, position });
|
||||
note.saveAttachment({ attachmentId, role, mime, title, content: decodedContent, position });
|
||||
} else {
|
||||
existingAttachment.role = role;
|
||||
existingAttachment.mime = mime;
|
||||
existingAttachment.position = position;
|
||||
if (content) {
|
||||
existingAttachment.setContent(content, { forceSave: true });
|
||||
if (decodedContent) {
|
||||
existingAttachment.setContent(decodedContent, { forceSave: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ export interface AttachmentRow {
|
||||
deleteId?: string;
|
||||
contentLength?: number;
|
||||
content?: Buffer | string;
|
||||
/** If set to `"base64"`, the `content` string will be decoded from base64 to binary before storage. */
|
||||
encoding?: "base64";
|
||||
}
|
||||
|
||||
export interface RevisionRow {
|
||||
|
||||
Reference in New Issue
Block a user