1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-03-03 15:32:30 +02:00

Desktop: Allow attaching a file from the Markdown editor for HTML notes

This commit is contained in:
Laurent Cozic 2023-10-31 16:53:47 +00:00
parent a95a66104d
commit a7dddaf2c4
13 changed files with 73 additions and 21 deletions

View File

@ -66,8 +66,10 @@ describe('MdToHtml', () => {
actualHtml,
'--------------------------------- Raw:',
actualHtml.split('\n'),
'--------------------------------- Expected:',
'--------------------------------- Expected (Lines)',
expectedHtml.split('\n'),
'--------------------------------- Expected (Text)',
expectedHtml,
'--------------------------------------------',
'',
];

View File

@ -0,0 +1,7 @@
<p><a href=":/62d16d1c1e28418da6624fa8742a7ed0" class="jop-noMdConv">Resource link</a></p>
<p><a href="https://example.com/ok" class="jop-noMdConv">ok</a></p>
<p><a href="http://example.com/ok" class="jop-noMdConv">ok</a></p>
<p><a href="mailto:name@email.com" class="jop-noMdConv">ok</a></p>
<p><a href="joplin://62d16d1c1e28418da6624fa8742a7ed0" class="jop-noMdConv">ok</a></p>
<p><a href="#" class="jop-noMdConv">not ok</a></p>
<p><a href="#" class="jop-noMdConv">not ok</a></p>

View File

@ -0,0 +1,13 @@
<a href=":/62d16d1c1e28418da6624fa8742a7ed0">Resource link</a>
<a href="https://example.com/ok">ok</a>
<a href="http://example.com/ok">ok</a>
<a href="mailto:name@email.com">ok</a>
<a href="joplin://62d16d1c1e28418da6624fa8742a7ed0">ok</a>
<a href="file:///etc/passwd">not ok</a>
<a href="data://blabla">not ok</a>

View File

@ -136,7 +136,11 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
editorRef.current.insertAtCursor(cmd.value.markdownTags.join('\n'));
} else if (cmd.value.type === 'files') {
const pos = cursorPositionToTextOffset(editorRef.current.getCursor(), props.content);
const newBody = await commandAttachFileToBody(props.content, cmd.value.paths, { createFileURL: !!cmd.value.createFileURL, position: pos });
const newBody = await commandAttachFileToBody(props.content, cmd.value.paths, {
createFileURL: !!cmd.value.createFileURL,
position: pos,
markupLanguage: props.contentMarkupLanguage,
});
editorRef.current.updateBody(newBody);
} else {
reg.logger().warn('CodeMirror: unsupported drop item: ', cmd);
@ -214,7 +218,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
const cursor = editorRef.current.getCursor();
const pos = cursorPositionToTextOffset(cursor, props.content);
const newBody = await commandAttachFileToBody(props.content, null, { position: pos });
const newBody = await commandAttachFileToBody(props.content, null, { position: pos, markupLanguage: props.contentMarkupLanguage });
if (newBody) editorRef.current.updateBody(newBody);
},
textNumberedList: () => {
@ -255,7 +259,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
},
};
// eslint-disable-next-line @seiyab/react-hooks/exhaustive-deps -- Old code before rule was applied
}, [props.content, props.visiblePanes, addListItem, wrapSelectionWithStrings, setEditorPercentScroll, setViewerPercentScroll, resetScroll]);
}, [props.content, props.visiblePanes, props.contentMarkupLanguage, addListItem, wrapSelectionWithStrings, setEditorPercentScroll, setViewerPercentScroll, resetScroll]);
const onEditorPaste = useCallback(async (event: any = null) => {
const resourceMds = await getResourcesFromPasteEvent(event);

View File

@ -151,6 +151,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef<NoteBodyEditor
editorCopyText, editorCutText, editorPaste,
editorContent: props.content,
visiblePanes: props.visiblePanes,
contentMarkupLanguage: props.contentMarkupLanguage,
});
useImperativeHandle(ref, () => {

View File

@ -7,6 +7,7 @@ import dialogs from '../../../../dialogs';
import { EditorCommandType } from '@joplin/editor/types';
import Logger from '@joplin/utils/Logger';
import CodeMirrorControl from '@joplin/editor/CodeMirror/CodeMirrorControl';
import { MarkupLanguage } from '@joplin/renderer';
const logger = Logger.create('CodeMirror 6 commands');
@ -40,6 +41,7 @@ interface Props {
selectionRange: { from: number; to: number };
visiblePanes: string[];
contentMarkupLanguage: MarkupLanguage;
}
const useEditorCommands = (props: Props) => {
@ -57,7 +59,7 @@ const useEditorCommands = (props: Props) => {
editorRef.current.insertText(cmd.markdownTags.join('\n'));
} else if (cmd.type === 'files') {
const pos = props.selectionRange.from;
const newBody = await commandAttachFileToBody(props.editorContent, cmd.paths, { createFileURL: !!cmd.createFileURL, position: pos });
const newBody = await commandAttachFileToBody(props.editorContent, cmd.paths, { createFileURL: !!cmd.createFileURL, position: pos, markupLanguage: props.contentMarkupLanguage });
editorRef.current.updateBody(newBody);
} else {
logger.warn('CodeMirror: unsupported drop item: ', cmd);
@ -92,7 +94,7 @@ const useEditorCommands = (props: Props) => {
insertText: (value: any) => editorRef.current.insertText(value),
attachFile: async () => {
const newBody = await commandAttachFileToBody(
props.editorContent, null, { position: props.selectionRange.from },
props.editorContent, null, { position: props.selectionRange.from, markupLanguage: props.contentMarkupLanguage },
);
if (newBody) {
editorRef.current.updateBody(newBody);
@ -129,7 +131,7 @@ const useEditorCommands = (props: Props) => {
}, [
props.visiblePanes, props.editorContent, props.editorCopyText, props.editorCutText, props.editorPaste,
props.selectionRange,
props.contentMarkupLanguage,
props.webviewRef, editorRef,
]);
};

View File

@ -2,9 +2,14 @@ import { CommandDeclaration } from '@joplin/lib/services/CommandService';
import { _ } from '@joplin/lib/locale';
import { joplinCommandToTinyMceCommands } from './NoteBody/TinyMCE/utils/joplinCommandToTinyMceCommands';
const workWithHtmlNotes = [
'attachFile',
];
export const enabledCondition = (commandName: string) => {
const markdownEditorOnly = !Object.keys(joplinCommandToTinyMceCommands).includes(commandName);
return `(!modalDialogVisible || gotoAnythingVisible) ${markdownEditorOnly ? '&& markdownEditorPaneVisible' : ''} && oneNoteSelected && noteIsMarkdown && !noteIsReadOnly`;
const noteMustBeMarkdown = !workWithHtmlNotes.includes(commandName);
return `(!modalDialogVisible || gotoAnythingVisible) ${markdownEditorOnly ? '&& markdownEditorPaneVisible' : ''} && oneNoteSelected ${noteMustBeMarkdown ? '&& noteIsMarkdown' : ''} && !noteIsReadOnly`;
};
const declarations: CommandDeclaration[] = [

View File

@ -9,6 +9,7 @@ import htmlUtils from '@joplin/lib/htmlUtils';
import rendererHtmlUtils, { extractHtmlBody } from '@joplin/renderer/htmlUtils';
import Logger from '@joplin/utils/Logger';
import { fileUriToPath } from '@joplin/utils/url';
import { MarkupLanguage } from '@joplin/renderer';
const joplinRendererUtils = require('@joplin/renderer').utils;
const { clipboard } = require('electron');
const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
@ -62,6 +63,7 @@ export async function commandAttachFileToBody(body: string, filePaths: string[]
options = {
createFileURL: false,
position: 0,
markupLanguage: MarkupLanguage.Markdown,
...options,
};
@ -79,6 +81,7 @@ export async function commandAttachFileToBody(body: string, filePaths: string[]
const newBody = await shim.attachFileToNoteBody(body, filePath, options.position, {
createFileURL: options.createFileURL,
resizeLargeImages: Setting.value('imageResizing'),
markupLanguage: options.markupLanguage,
});
if (!newBody) {

View File

@ -727,7 +727,7 @@ class NoteScreenComponent extends BaseScreenComponent {
resource = await Resource.save(resource, { isNew: true });
const resourceTag = Resource.markdownTag(resource);
const resourceTag = Resource.markupTag(resource);
const newNote = { ...this.state.note };

View File

@ -16,6 +16,8 @@ import itemCanBeEncrypted from './utils/itemCanBeEncrypted';
import { getEncryptionEnabled } from '../services/synchronizer/syncInfoUtils';
import ShareService from '../services/share/ShareService';
import { SaveOptions } from './utils/types';
import { MarkupLanguage } from '@joplin/renderer';
import { htmlentities } from '@joplin/utils/html';
export default class Resource extends BaseItem {
@ -231,18 +233,28 @@ export default class Resource extends BaseItem {
return { path: encryptedPath, resource: resourceCopy };
}
public static markdownTag(resource: any) {
public static markupTag(resource: any, markupLanguage: MarkupLanguage = MarkupLanguage.Markdown) {
let tagAlt = resource.alt ? resource.alt : resource.title;
if (!tagAlt) tagAlt = '';
const lines = [];
if (Resource.isSupportedImageMimeType(resource.mime)) {
lines.push('![');
lines.push(markdownUtils.escapeTitleText(tagAlt));
lines.push(`](:/${resource.id})`);
if (markupLanguage === MarkupLanguage.Markdown) {
lines.push('![');
lines.push(markdownUtils.escapeTitleText(tagAlt));
lines.push(`](:/${resource.id})`);
} else {
const altHtml = tagAlt ? `alt="${htmlentities(tagAlt)}"` : '';
lines.push(`<img src=":/${resource.id}" ${altHtml}/>`);
}
} else {
lines.push('[');
lines.push(markdownUtils.escapeTitleText(tagAlt));
lines.push(`](:/${resource.id})`);
if (markupLanguage === MarkupLanguage.Markdown) {
lines.push('[');
lines.push(markdownUtils.escapeTitleText(tagAlt));
lines.push(`](:/${resource.id})`);
} else {
const altHtml = tagAlt ? `alt="${htmlentities(tagAlt)}"` : '';
lines.push(`<a href=":/${resource.id}" ${altHtml}>${htmlentities(tagAlt ? tagAlt : resource.id)}</a>`);
}
}
return lines.join('');
}
@ -444,7 +456,7 @@ export default class Resource extends BaseItem {
await Note.save({
title: _('Attachment conflict: "%s"', resource.title),
body: _('There was a [conflict](%s) on the attachment below.\n\n%s', 'https://joplinapp.org/help/apps/conflict', Resource.markdownTag(conflictResource)),
body: _('There was a [conflict](%s) on the attachment below.\n\n%s', 'https://joplinapp.org/help/apps/conflict', Resource.markupTag(conflictResource)),
parent_id: await this.resourceConflictFolderId(),
}, { changeSource: ItemChange.SOURCE_SYNC });
}

View File

@ -63,7 +63,7 @@ describe('services/ResourceService', () => {
await service.indexNoteResources();
await Note.save({ id: note2.id, body: Resource.markdownTag(resource1) });
await Note.save({ id: note2.id, body: Resource.markupTag(resource1) });
await service.indexNoteResources();

View File

@ -332,7 +332,7 @@ function shimInit(options = null) {
};
shim.attachFileToNoteBody = async function(noteBody, filePath, position = null, options = null) {
options = { createFileURL: false, ...options };
options = { createFileURL: false, markupLanguage: 1, ...options };
const { basename } = require('path');
const { escapeTitleText } = require('./markdownUtils').default;
@ -353,7 +353,7 @@ function shimInit(options = null) {
if (noteBody && position) newBody.push(noteBody.substr(0, position));
if (!options.createFileURL) {
newBody.push(Resource.markdownTag(resource));
newBody.push(Resource.markupTag(resource, options.markupLanguage));
} else {
const filename = escapeTitleText(basename(filePath)); // to get same filename as standard drag and drop
const fileURL = `[${filename}](${toFileProtocolPath(filePath)})`;
@ -366,6 +366,7 @@ function shimInit(options = null) {
};
shim.attachFileToNote = async function(note, filePath, position = null, options = null) {
if (note.markup_language) options.markupLanguage = note.markup_language;
const newBody = await shim.attachFileToNoteBody(note.body, filePath, position, options);
if (!newBody) return null;

View File

@ -159,12 +159,14 @@ class HtmlUtils {
.replace(/</g, '&lt;');
}
// This is tested in sanitize_links.md
private isAcceptedUrl(url: string, allowedFilePrefixes: string[]): boolean {
url = url.toLowerCase();
if (url.startsWith('https://') ||
url.startsWith('http://') ||
url.startsWith('mailto://') ||
url.startsWith('mailto:') ||
url.startsWith('joplin://') ||
!!url.match(/:\/[0-9a-zA-Z]{32}/) ||
// We also allow anchors but only with a specific set of a characters.
// Fixes https://github.com/laurent22/joplin/issues/8286
!!url.match(/^#[a-zA-Z0-9-]+$/)) return true;