Compare commits

...

1 Commits

Author SHA1 Message Date
01zulfi
78837454ba editor: fetch attachment filename from db && live update on rename
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2026-06-12 12:36:06 +05:00
17 changed files with 157 additions and 28 deletions

View File

@@ -181,6 +181,24 @@ const Actions = ({
setFilename(value);
setAttachments();
eSendEvent(eDBItemUpdate, attachment.id);
const relations = await db.relations
.to(attachment, "note")
.get();
relations
.map((relation) => relation.fromId)
.forEach(async (id) => {
useTabStore.getState().forEachNoteTab(id, async (tab) => {
editorController.current?.commands.updateAttachment(
{
hash: attachment.hash,
filename: value
},
tab.id
);
});
});
ToastManager.show({
message: `Attachment renamed to ${value}`,
type: "success"

View File

@@ -183,11 +183,11 @@ class Commands {
await this.sendCommand("insertAttachment", attachment, tabId);
};
setAttachmentProgress = async (
attachmentProgress: Partial<Attachment>,
updateAttachment = async (
attachmentAttributes: Partial<Attachment>,
tabId: string
) => {
await this.sendCommand("setAttachmentProgress", attachmentProgress, tabId);
await this.sendCommand("updateAttachment", attachmentAttributes, tabId);
};
insertImage = async (

View File

@@ -578,6 +578,21 @@ export const useEditorEvents = (
break;
}
case EditorEvents.getAttachmentMetaData: {
const { hash } = editorMessage.value as Pick<Attachment, "hash">;
const attachment = await db
.sql()
.selectFrom("attachments")
.where("hash", "=", hash)
.select("filename")
.executeTakeFirst();
editor.postMessage(NativeEvents.resolve, {
resolverId: editorMessage.resolverId,
data: attachment ? { filename: attachment.filename } : undefined
});
break;
}
case EditorEvents.pro:
if (editor.state.current?.isFocused) {
editor.state.current.isFocused = true;

View File

@@ -68,7 +68,7 @@ export const useAttachmentStore = create<AttachmentStore>((set, get) => ({
remove: (hash) => {
const progress = get().progress;
if (!progress) return;
editorController.current?.commands.setAttachmentProgress(
editorController.current?.commands.updateAttachment(
{
hash: hash,
progress: 100
@@ -85,7 +85,7 @@ export const useAttachmentStore = create<AttachmentStore>((set, get) => ({
const progressPercentage =
type === "upload" ? sent / total : recieved / total;
editorController.current?.commands.setAttachmentProgress(
editorController.current?.commands.updateAttachment(
{
hash: hash,
//@ts-ignore

View File

@@ -519,10 +519,9 @@ export function Editor(props: EditorProps) {
AppEvents.UPDATE_ATTACHMENT_PROGRESS,
({ hash, loaded, total }: AttachmentProgress) => {
const editor = useEditorManager.getState().getEditor(id)?.editor;
editor?.sendAttachmentProgress(
hash,
Math.round((loaded / total) * 100)
);
editor?.updateAttachment(hash, {
progress: Math.round((loaded / total) * 100)
});
}
);
@@ -644,6 +643,17 @@ export function Editor(props: EditorProps) {
return result;
}}
onGetAttachmentMetaData={async (hash) => {
const result = await db
.sql()
.selectFrom("attachments")
.where("hash", "=", hash)
.select("filename")
.executeTakeFirst();
if (!result || !result.filename) return undefined;
return { filename: result.filename };
}}
onAttachFiles={async (files) => {
await AttachFilesDialog.show({
files,

View File

@@ -90,6 +90,9 @@ type TipTapProps = {
attachment: Pick<Attachment, "hash" | "type">
) => Promise<string | undefined>)
| undefined;
onGetAttachmentMetaData?: (
hash: string
) => Promise<Pick<Attachment, "filename"> | undefined>;
onAttachFiles?: (files: File[]) => void;
onInsertInternalLink?: (
attributes?: LinkAttributes
@@ -170,6 +173,7 @@ function TipTap(props: TipTapProps) {
onDownloadAttachment,
onPreviewAttachment,
onGetAttachmentData,
onGetAttachmentMetaData,
onAttachFiles,
onInsertInternalLink,
onContentChange,
@@ -423,6 +427,7 @@ function TipTap(props: TipTapProps) {
previewAttachment: onPreviewAttachment,
createInternalLink: onInsertInternalLink,
getAttachmentData: onGetAttachmentData,
getAttachmentMetaData: onGetAttachmentMetaData,
openLink: async (url, openInNewTab) => {
const link = parseInternalLink(url);
if (link && link.type === "note") {
@@ -750,13 +755,11 @@ function toIEditor(editor: Editor): IEditor {
file.type === "image"
? editor.commands.insertImage(file)
: editor.commands.insertAttachment(file),
sendAttachmentProgress: (hash, progress) =>
editor.commands.updateAttachment(
{
progress
},
{ query: (a) => a.hash === hash, preventUpdate: true }
),
updateAttachment: (hash, attributes) =>
editor.commands.updateAttachment(attributes, {
query: (a) => a.hash === hash,
preventUpdate: true
}),
startSearch: () => editor.commands.startSearch(),
getContent: () =>
getHTMLFromFragment(editor.state.doc.content, editor.schema),

View File

@@ -49,7 +49,7 @@ export interface IEditor {
redo: () => void;
updateContent: (content: string) => void;
attachFile: (file: Attachment) => void;
sendAttachmentProgress: (hash: string, progress: number) => void;
updateAttachment: (hash: string, attributes: Partial<Attachment>) => void;
startSearch: () => void;
getContent: () => string;
getSelection: () => { from: number; to: number };

View File

@@ -29,6 +29,7 @@ import { createWriteStream } from "../utils/stream-saver";
import { Attachment } from "@notesnook/core";
import { TaskManager } from "../common/task-manager";
import { strings } from "@notesnook/intl";
import { useEditorManager } from "../components/editor/manager";
let abortController: AbortController | undefined = undefined;
class AttachmentStore extends BaseStore<AttachmentStore> {
@@ -116,11 +117,31 @@ class AttachmentStore extends BaseStore<AttachmentStore> {
};
rename = async (hash: string, newName: string) => {
await db.attachments.add({
const attachmentId = await db.attachments.add({
hash,
filename: newName
});
await this.get().refresh();
if (!attachmentId) return;
const linkedNotes = (
await db.relations
.to({ id: attachmentId, type: "attachment" }, "note")
.get()
).map((l) => l.fromId);
const sessions = useEditorStore.getState().sessions;
for (const session of sessions) {
if (!("note" in session) || !session.note.id || !session.note.contentId) {
continue;
}
if (!linkedNotes.includes(session.note.id)) {
continue;
}
const editor = useEditorManager.getState().getEditor(session.id)?.editor;
editor?.updateAttachment(hash, { filename: newName });
}
};
permanentDelete = async (attachment: Attachment) => {

View File

@@ -162,6 +162,11 @@ const Tiptap = ({
attachment
) as Promise<string | undefined>;
},
async getAttachmentMetaData(hash) {
return globalThis.editorControllers[tab.id]?.getAttachmentMetaData(
hash
);
},
createInternalLink(attributes) {
return postAsyncWithTimeout(EditorEvents.createInternalLink, {
attributes

View File

@@ -102,6 +102,21 @@ const Tiptap = ({
});
});
},
getAttachmentMetaData(hash) {
return new Promise<{ filename: string } | undefined>(
(resolve, reject) => {
const resolverId = randId("get_attachment_metadata");
pendingResolvers[resolverId] = (data) => {
delete pendingResolvers[resolverId];
resolve(data);
};
post(EditorEvents.getAttachmentMetaData, {
hash,
resolverId: resolverId
});
}
);
},
element: getContentDiv(),
editable: false,
editorProps: {

View File

@@ -93,6 +93,9 @@ export type EditorController = {
countWords: (ms: number) => void;
copyToClipboard: (text: string) => void;
getAttachmentData: (attachment: Partial<Attachment>) => Promise<string>;
getAttachmentMetaData: (
hash: string
) => Promise<{ filename: string } | undefined>;
updateTab: () => void;
loading: boolean;
setLoading: (value: boolean) => void;
@@ -454,6 +457,15 @@ export function useEditorController({
});
};
const getAttachmentMetaData = (hash: string) => {
return postAsyncWithTimeout<{ filename: string } | undefined>(
EditorEvents.getAttachmentMetaData,
{
hash
}
);
};
const scrollToSearchResult = useCallback((index: number) => {
const marks = document.getElementsByTagName("nn-search-result");
if (marks.length > index) {
@@ -490,6 +502,7 @@ export function useEditorController({
countWords,
copyToClipboard,
getAttachmentData,
getAttachmentMetaData,
getContentDiv,
updateTab: () => {
// When the tab is focused, we apply any updates to content that were recieved when

View File

@@ -135,15 +135,15 @@ globalThis.commands = {
}
},
setAttachmentProgress: (
attachmentProgress: Partial<Attachment>,
updateAttachment: (
attachmentAttributes: Partial<Attachment>,
tabId: number
) => {
const editor = editors[tabId];
if (editor) {
editor.commands.updateAttachment(attachmentProgress, {
editor.commands.updateAttachment(attachmentAttributes, {
preventUpdate: true,
query: (attachment) => attachment.hash === attachmentProgress.hash
query: (attachment) => attachment.hash === attachmentAttributes.hash
});
}
},

View File

@@ -39,6 +39,7 @@ export const EditorEvents = {
previewAttachment: "editor-event:preview-attachment",
copyToClipboard: "editor-events:copy-to-clipboard",
getAttachmentData: "editor-events:get-attachment-data",
getAttachmentMetaData: "editor-events:get-attachment-metadata",
tabsChanged: "editor-events:tabs-changed",
showTabs: "editor-events:show-tabs",
tabFocused: "editor-events:tab-focused",

View File

@@ -101,7 +101,9 @@ export const AttachmentNode = Node.create<AttachmentOptions>({
addNodeView() {
return createNodeView(AttachmentComponent, {
shouldUpdate: ({ attrs: prev }, { attrs: next }) => {
return prev.progress !== next.progress;
return (
prev.progress !== next.progress || prev.filename !== next.filename
);
},
forceEnableSelection: true
});

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Box, Text } from "@theme-ui/components";
import { FileAttachment } from "./types.js";
import { useRef, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { Icon } from "@notesnook/ui";
import { Icons } from "../../toolbar/icons.js";
import { ReactNodeViewProps } from "../react/index.js";
@@ -28,11 +28,21 @@ import { DesktopOnly } from "../../components/responsive/index.js";
import { formatBytes } from "@notesnook/common";
export function AttachmentComponent(props: ReactNodeViewProps<FileAttachment>) {
const { editor, node, selected } = props;
const { filename, size, progress } = node.attrs;
const { editor, node, selected, updateAttributes } = props;
const { filename, size, progress, hash } = node.attrs;
const elementRef = useRef<HTMLSpanElement>();
const [isDragging, setIsDragging] = useState(false);
useEffect(() => {
if (!hash) return;
editor.storage.getAttachmentMetaData?.(hash).then((meta) => {
if (meta?.filename && meta.filename !== filename) {
updateAttributes({ filename: meta.filename });
}
});
}, [hash, filename]);
return (
<Box
ref={elementRef}

View File

@@ -36,13 +36,23 @@ const SAMPLE_AUDIO = toBlobURL(
);
export function AudioComponent(props: ReactNodeViewProps<AudioAttachment>) {
const { editor, node, selected } = props;
const { editor, node, selected, updateAttributes } = props;
const { filename, size, progress, mime, hash } = node.attrs;
const elementRef = useRef<HTMLDivElement>();
const [isDragging, setIsDragging] = useState(false);
const isLoading = useRef(false);
const [error, setError] = useState<string>();
useEffect(() => {
if (!hash) return;
editor.storage.getAttachmentMetaData?.(hash).then((meta) => {
if (meta?.filename && meta.filename !== filename) {
updateAttributes({ filename: meta.filename });
}
});
}, [hash, filename]);
useEffect(() => {
return () => {
if (hash) {

View File

@@ -107,6 +107,9 @@ interface TiptapStorage {
attachment: Pick<Attachment, "hash" | "type">
) => Promise<string | undefined>)
| undefined;
getAttachmentMetaData?: (
hash: string
) => Promise<Pick<Attachment, "filename"> | undefined>;
}
declare module "@tiptap/core" {
@@ -144,6 +147,7 @@ const useTiptap = (
) => {
const {
getAttachmentData,
getAttachmentMetaData,
downloadAttachment,
openAttachmentPicker,
previewAttachment,
@@ -395,6 +399,7 @@ const useTiptap = (
editor.storage.createInternalLink = createInternalLink;
editor.storage.getAttachmentData = getAttachmentData;
editor.storage.downloadCsvTable = downloadCsvTable;
editor.storage.getAttachmentMetaData = getAttachmentMetaData;
if (onBeforeCreate) onBeforeCreate({ editor });
},
@@ -413,7 +418,8 @@ const useTiptap = (
timeFormat,
editorProps,
copyToClipboard,
createInternalLink
createInternalLink,
getAttachmentMetaData
]
);