diff --git a/packages/app-cli/tests/MdToHtml.ts b/packages/app-cli/tests/MdToHtml.ts index 05efface08..67150002ea 100644 --- a/packages/app-cli/tests/MdToHtml.ts +++ b/packages/app-cli/tests/MdToHtml.ts @@ -66,8 +66,10 @@ describe('MdToHtml', () => { actualHtml, '--------------------------------- Raw:', actualHtml.split('\n'), - '--------------------------------- Expected:', + '--------------------------------- Expected (Lines)', expectedHtml.split('\n'), + '--------------------------------- Expected (Text)', + expectedHtml, '--------------------------------------------', '', ]; diff --git a/packages/app-cli/tests/md_to_html/sanitize_links.html b/packages/app-cli/tests/md_to_html/sanitize_links.html new file mode 100644 index 0000000000..59bb8c0a3c --- /dev/null +++ b/packages/app-cli/tests/md_to_html/sanitize_links.html @@ -0,0 +1,7 @@ +

Resource link

+

ok

+

ok

+

ok

+

ok

+

not ok

+

not ok

diff --git a/packages/app-cli/tests/md_to_html/sanitize_links.md b/packages/app-cli/tests/md_to_html/sanitize_links.md new file mode 100644 index 0000000000..1f5d67c85c --- /dev/null +++ b/packages/app-cli/tests/md_to_html/sanitize_links.md @@ -0,0 +1,13 @@ +Resource link + +ok + +ok + +ok + +ok + +not ok + +not ok diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx index eff98e671d..58bd44a1b0 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v5/CodeMirror.tsx @@ -136,7 +136,11 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef { @@ -255,7 +259,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: ForwardedRef { const resourceMds = await getResourcesFromPasteEvent(event); diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/CodeMirror.tsx b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/CodeMirror.tsx index 5a65451f5b..2c2dd24b3f 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/CodeMirror.tsx +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/CodeMirror.tsx @@ -151,6 +151,7 @@ const CodeMirror = (props: NoteBodyEditorProps, ref: ForwardedRef { diff --git a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.ts b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.ts index e415449e12..cc43677e12 100644 --- a/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.ts +++ b/packages/app-desktop/gui/NoteEditor/NoteBody/CodeMirror/v6/useEditorCommands.ts @@ -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, ]); }; diff --git a/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts b/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts index 34b38512b6..a076b5822e 100644 --- a/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts +++ b/packages/app-desktop/gui/NoteEditor/editorCommandDeclarations.ts @@ -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[] = [ diff --git a/packages/app-desktop/gui/NoteEditor/utils/resourceHandling.ts b/packages/app-desktop/gui/NoteEditor/utils/resourceHandling.ts index 1f1e4b8bf4..efdefced03 100644 --- a/packages/app-desktop/gui/NoteEditor/utils/resourceHandling.ts +++ b/packages/app-desktop/gui/NoteEditor/utils/resourceHandling.ts @@ -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) { diff --git a/packages/app-mobile/components/screens/Note.tsx b/packages/app-mobile/components/screens/Note.tsx index ac6a3ea354..17835a1c31 100644 --- a/packages/app-mobile/components/screens/Note.tsx +++ b/packages/app-mobile/components/screens/Note.tsx @@ -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 }; diff --git a/packages/lib/models/Resource.ts b/packages/lib/models/Resource.ts index 264222d86b..42316f1806 100644 --- a/packages/lib/models/Resource.ts +++ b/packages/lib/models/Resource.ts @@ -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(``); + } } 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(`${htmlentities(tagAlt ? tagAlt : resource.id)}`); + } } 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 }); } diff --git a/packages/lib/services/ResourceService.test.ts b/packages/lib/services/ResourceService.test.ts index 3fa956e6dd..a9f65a5d78 100644 --- a/packages/lib/services/ResourceService.test.ts +++ b/packages/lib/services/ResourceService.test.ts @@ -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(); diff --git a/packages/lib/shim-init-node.js b/packages/lib/shim-init-node.js index 0bc6b8fec6..a073aea825 100644 --- a/packages/lib/shim-init-node.js +++ b/packages/lib/shim-init-node.js @@ -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; diff --git a/packages/renderer/htmlUtils.ts b/packages/renderer/htmlUtils.ts index e608f3c913..e32b5312f4 100644 --- a/packages/renderer/htmlUtils.ts +++ b/packages/renderer/htmlUtils.ts @@ -159,12 +159,14 @@ class HtmlUtils { .replace(/