Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 67 additions & 7 deletions src/application/services/useNoteEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { type Ref, computed, ref, toValue, watch } from 'vue';
import { useAppState } from './useAppState';
import type EditorTool from '@/domain/entities/EditorTool';
import { type NoteContent } from '@/domain/entities/Note';
import { editorToolsService } from '@/domain';
import type { EditorjsToolsConfig } from '@/domain/entities/EditorTool';
import { editorToolsService, noteSettingsService } from '@/domain';
import type { EditorjsConfigTool, EditorjsToolsConfig } from '@/domain/entities/EditorTool';
import { useI18n } from 'vue-i18n';

interface UseNoteEditorOptions {
Expand All @@ -21,6 +21,12 @@ interface UseNoteEditorOptions {
* Flag indicating that user can edit the note
*/
canEdit: Ref<boolean>;

/**
* Note id to build image upload endpoint
* Null for new notes
*/
noteId: Ref<string | null>;
}

interface UseNoteEditorComposableState {
Expand Down Expand Up @@ -131,13 +137,67 @@ export const useNoteEditor = function useNoteEditor(options: UseNoteEditorOption

return Object.fromEntries(
loadedToolsWithoutParagraph
.map(toolClassAndInfo => [
toolClassAndInfo.tool.name,
{
.map((toolClassAndInfo) => {
const toolConfig: { class: EditorjsConfigTool; inlineToolbar: boolean; config?: Record<string, unknown> } = {
class: toolClassAndInfo.class,
inlineToolbar: true,
},
])
};

/**
* Add image tool config with a custom uploader
* that uses the authorized note attachment upload method
*/
if (toolClassAndInfo.tool.name === 'image') {
const noteId = toValue(options.noteId);

if (noteId !== null) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about images in new notes?

toolConfig.config = {
Comment on lines +150 to +154
/**
* The image tool internally accesses `this.config.endpoints.byFile`
* when a file is selected, even when a custom uploader is provided
* Provide an empty endpoints object to prevent a TypeError
*/
endpoints: {
byFile: '',
},
features: {
caption: 'optional',
},
uploader: {
/**
* Uploads file using the existing authorized repository method
* @param file - file selected in the editor
*/
uploadByFile: async (file: File): Promise<{ success: 1; file: { url: string } }> => {
const url = await noteSettingsService.uploadImage(noteId, file);

return {
success: 1,
file: {
url,
},
};
},
/**
* When a user pastes an image URL, use it as-is
* without uploading the image to the server
* @param url - image URL pasted by the user
*/
uploadByUrl: (url: string): Promise<{ success: 1; file: { url: string } }> => {
return Promise.resolve({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to save file by url first, then substitute our own internal path to the file.url

success: 1,
file: {
url,
},
});
},
},
};
}
}

return [toolClassAndInfo.tool.name, toolConfig];
})
);
}

Expand Down
2 changes: 1 addition & 1 deletion src/domain/entities/EditorTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export type EditorjsConfigTool = ToolSettings | ToolConstructable;
/**
* Editor.js tools config — map of tool name to its class and inline toolbar flag
*/
export type EditorjsToolsConfig = Record<string, { class: EditorjsConfigTool; inlineToolbar: boolean }>;
export type EditorjsToolsConfig = Record<string, { class: EditorjsConfigTool; inlineToolbar: boolean; config?: Record<string, unknown> }>;

/**
* Editor tool info alogn with its plugin's class ready to use
Expand Down
8 changes: 8 additions & 0 deletions src/domain/noteAttachmentUploader.repository.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,12 @@ export default interface NoteAttachmentUploaderRepositoryInterface {
* @returns file binary data
*/
load: (noteId: Note['id'], key: string) => Promise<Blob>;

/**
* Build a URL to load the note attachment by key
* @param noteId - identifier for note to get attachment
* @param key - file key on server side
* @returns URL of the attachment
*/
getFileUrl: (noteId: Note['id'], key: string) => string;
}
13 changes: 13 additions & 0 deletions src/domain/noteSettings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,19 @@ export default class NoteSettingsService {
return await this.patchNoteSettingsByNoteId(id, { cover: key });
}

/**
* Upload an image to be used in the note editor
* Uses the authorized transport via noteAttachmentRepository
* @param id - note id
* @param data - image binary data
* @returns URL of the uploaded image that can be rendered in the editor
*/
public async uploadImage(id: NoteId, data: Blob): Promise<string> {
const key = await this.noteAttachmentRepository.upload(id, data);

return this.noteAttachmentRepository.getFileUrl(id, key);
}

/**
* Revoke invitation hash
* @param id - Note id
Expand Down
10 changes: 10 additions & 0 deletions src/infrastructure/noteAttachmentUploader.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,14 @@ export default class NoteAttachmentUploaderRepository implements NoteAttachmentU
public async load(noteId: Note['id'], key: string): Promise<Blob> {
return await this.transport.getBlob(`/upload/${noteId}/${key}`);
}

/**
* Build a URL to load the note attachment by key
* @param noteId - identifier for note to get attachment
* @param key - file key on server side
* @returns URL of the attachment
*/
public getFileUrl(noteId: Note['id'], key: string): string {
return this.transport.getBaseUrl() + `/upload/${noteId}/${key}`;
}
}
7 changes: 7 additions & 0 deletions src/infrastructure/transport/fetch.transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ export default class FetchTransport {
private readonly options?: FetchTransportOptions
) {}

/**
* Returns the base URL of the transport
*/
public getBaseUrl(): string {
return this.baseUrl;
}

/**
* Gets specific resource
* @template Response - Response data type
Expand Down
1 change: 1 addition & 0 deletions src/presentation/pages/HistoryVersion.vue
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ const { isEditorReady, editorConfig } = useNoteEditor({
noteTools: historyTools,
noteContentResolver: () => historyContent.value,
canEdit,
noteId,
});

/**
Expand Down
1 change: 1 addition & 0 deletions src/presentation/pages/Note.vue
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ const { isEditorReady, editorConfig } = useNoteEditor({
noteTools,
noteContentResolver: () => note.value?.content,
canEdit,
noteId,
});

/**
Expand Down
Loading