mirror of
https://github.com/laurent22/joplin.git
synced 2026-05-03 13:00:11 -05:00
update
This commit is contained in:
@@ -247,6 +247,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/index.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/normalizeAccelerator.test.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/normalizeAccelerator.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.test.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useEditorSearchExtension.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useEditorSearchHandler.js
|
||||
|
||||
@@ -221,6 +221,7 @@ packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/index.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/normalizeAccelerator.test.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/normalizeAccelerator.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/types.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.test.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useContextMenu.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useEditorSearchExtension.js
|
||||
packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/utils/useEditorSearchHandler.js
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import { getResourceIdFromMarkup } from './useContextMenu';
|
||||
|
||||
describe('useContextMenu', () => {
|
||||
const resourceId = 'a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4';
|
||||
const resourceId2 = 'b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5';
|
||||
|
||||
it('should return resource ID when cursor is inside markdown image', () => {
|
||||
const line = ``;
|
||||
expect(getResourceIdFromMarkup(line, 0)).toBe(resourceId);
|
||||
expect(getResourceIdFromMarkup(line, 15)).toBe(resourceId);
|
||||
expect(getResourceIdFromMarkup(line, line.length - 1)).toBe(resourceId);
|
||||
});
|
||||
|
||||
it('should return null when cursor is outside markdown image', () => {
|
||||
const line = `Some text  more text`;
|
||||
expect(getResourceIdFromMarkup(line, 5)).toBeNull();
|
||||
expect(getResourceIdFromMarkup(line, line.length - 5)).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle markdown image without alt text', () => {
|
||||
const line = ``;
|
||||
expect(getResourceIdFromMarkup(line, 5)).toBe(resourceId);
|
||||
});
|
||||
|
||||
it('should return resource ID when cursor is inside HTML img tag', () => {
|
||||
const line = `<img src=":/${resourceId}" />`;
|
||||
expect(getResourceIdFromMarkup(line, 10)).toBe(resourceId);
|
||||
});
|
||||
|
||||
it('should handle HTML img tag with additional attributes', () => {
|
||||
const line = `<img alt="test" src=":/${resourceId}" width="100" />`;
|
||||
expect(getResourceIdFromMarkup(line, 25)).toBe(resourceId);
|
||||
});
|
||||
|
||||
it('should return null when cursor is outside HTML img tag', () => {
|
||||
const line = `text <img src=":/${resourceId}" /> more`;
|
||||
expect(getResourceIdFromMarkup(line, 2)).toBeNull();
|
||||
expect(getResourceIdFromMarkup(line, line.length - 2)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return correct resource ID when multiple images on same line', () => {
|
||||
const line = ` `;
|
||||
expect(getResourceIdFromMarkup(line, 10)).toBe(resourceId);
|
||||
expect(getResourceIdFromMarkup(line, 50)).toBe(resourceId2);
|
||||
});
|
||||
|
||||
it('should return null for empty line', () => {
|
||||
expect(getResourceIdFromMarkup('', 0)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for line without images', () => {
|
||||
expect(getResourceIdFromMarkup('Just some regular text', 10)).toBeNull();
|
||||
});
|
||||
|
||||
it('should return null for non-resource links', () => {
|
||||
const line = '';
|
||||
expect(getResourceIdFromMarkup(line, 10)).toBeNull();
|
||||
});
|
||||
|
||||
it('should handle cursor at exact boundaries of image markup', () => {
|
||||
const line = ``;
|
||||
expect(getResourceIdFromMarkup(line, 0)).toBe(resourceId);
|
||||
expect(getResourceIdFromMarkup(line, line.length)).toBe(resourceId);
|
||||
});
|
||||
});
|
||||
@@ -16,6 +16,42 @@ import { menuItems } from '../../../utils/contextMenu';
|
||||
import isItemId from '@joplin/lib/models/utils/isItemId';
|
||||
import { extractResourceUrls } from '@joplin/lib/urlUtils';
|
||||
|
||||
// Extract resource ID from image markup at a given cursor position within a line.
|
||||
// Returns the resource ID if the cursor is within an image markup, null otherwise.
|
||||
export const getResourceIdFromMarkup = (lineContent: string, cursorPosInLine: number): string | null => {
|
||||
const resourceUrls = extractResourceUrls(lineContent);
|
||||
if (!resourceUrls.length) return null;
|
||||
|
||||
for (const resourceInfo of resourceUrls) {
|
||||
const resourcePattern = new RegExp(`[:](/?${resourceInfo.itemId})`, 'g');
|
||||
let match;
|
||||
while ((match = resourcePattern.exec(lineContent)) !== null) {
|
||||
// Look backwards for ![ or <img
|
||||
let markupStart = lineContent.lastIndexOf('![', match.index);
|
||||
const imgTagStart = lineContent.lastIndexOf('<img', match.index);
|
||||
if (imgTagStart > markupStart) markupStart = imgTagStart;
|
||||
|
||||
if (markupStart === -1) continue;
|
||||
|
||||
// Find the end of the markup
|
||||
let markupEnd: number;
|
||||
if (lineContent[markupStart] === '!') {
|
||||
markupEnd = lineContent.indexOf(')', match.index);
|
||||
if (markupEnd !== -1) markupEnd += 1;
|
||||
} else {
|
||||
markupEnd = lineContent.indexOf('>', match.index);
|
||||
if (markupEnd !== -1) markupEnd += 1;
|
||||
}
|
||||
|
||||
if (markupEnd !== -1 && cursorPosInLine >= markupStart && cursorPosInLine <= markupEnd) {
|
||||
return resourceInfo.itemId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const Menu = bridge().Menu;
|
||||
const MenuItem = bridge().MenuItem;
|
||||
const menuUtils = new MenuUtils(CommandService.instance());
|
||||
@@ -94,8 +130,7 @@ const useContextMenu = (props: ContextMenuProps) => {
|
||||
return clickedElement?.closest(`.${imageClassName}`) as HTMLElement | null;
|
||||
};
|
||||
|
||||
// Extract resource ID from image markup at cursor position
|
||||
const getResourceIdFromMarkup = (): string | null => {
|
||||
const getResourceIdAtCursor = (): string | null => {
|
||||
if (!editorRef.current) return null;
|
||||
|
||||
const editor = editorRef.current.editor;
|
||||
@@ -104,46 +139,7 @@ const useContextMenu = (props: ContextMenuProps) => {
|
||||
const state = editor.state;
|
||||
const cursorPos = state.selection.main.head;
|
||||
const line = state.doc.lineAt(cursorPos);
|
||||
const lineContent = line.text;
|
||||
const cursorPosInLine = cursorPos - line.from;
|
||||
|
||||
// Get all resource URLs from the line
|
||||
const resourceUrls = extractResourceUrls(lineContent);
|
||||
if (!resourceUrls.length) return null;
|
||||
|
||||
// Find which resource (if any) the cursor is within
|
||||
for (const resourceInfo of resourceUrls) {
|
||||
// Find the position of this resource ID in the line
|
||||
const resourcePattern = new RegExp(`[:](/?${resourceInfo.itemId})`, 'g');
|
||||
let match;
|
||||
while ((match = resourcePattern.exec(lineContent)) !== null) {
|
||||
// Expand to find the full image markup containing this resource
|
||||
// Look backwards for ![ or <img
|
||||
let markupStart = lineContent.lastIndexOf('![', match.index);
|
||||
const imgTagStart = lineContent.lastIndexOf('<img', match.index);
|
||||
if (imgTagStart > markupStart) markupStart = imgTagStart;
|
||||
|
||||
if (markupStart === -1) continue;
|
||||
|
||||
// Find the end of the markup
|
||||
let markupEnd: number;
|
||||
if (lineContent[markupStart] === '!') {
|
||||
// Markdown image: find closing )
|
||||
markupEnd = lineContent.indexOf(')', match.index);
|
||||
if (markupEnd !== -1) markupEnd += 1;
|
||||
} else {
|
||||
// HTML img: find closing >
|
||||
markupEnd = lineContent.indexOf('>', match.index);
|
||||
if (markupEnd !== -1) markupEnd += 1;
|
||||
}
|
||||
|
||||
if (markupEnd !== -1 && cursorPosInLine >= markupStart && cursorPosInLine <= markupEnd) {
|
||||
return resourceInfo.itemId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
return getResourceIdFromMarkup(line.text, cursorPos - line.from);
|
||||
};
|
||||
|
||||
const showImageContextMenu = async (resourceId: string) => {
|
||||
@@ -207,7 +203,7 @@ const useContextMenu = (props: ContextMenuProps) => {
|
||||
}
|
||||
|
||||
// Check if right-clicking on image markup text
|
||||
const markupResourceId = getResourceIdFromMarkup();
|
||||
const markupResourceId = getResourceIdAtCursor();
|
||||
if (markupResourceId && pointerInsideEditor(params)) {
|
||||
event.preventDefault();
|
||||
await showImageContextMenu(markupResourceId);
|
||||
|
||||
Reference in New Issue
Block a user