use marked instead of commonmark to convert MD to HTML, which supports e.g. tables, closes #2026

This commit is contained in:
zadam
2023-07-15 10:31:50 +02:00
parent 27e6fa9526
commit 64b86b2666
12 changed files with 91 additions and 110 deletions
@@ -23,8 +23,6 @@ const CODE_MIRROR = {
const ESLINT = {js: ["libraries/eslint.js"]};
const COMMONMARK = {js: ["libraries/commonmark.min.js"]};
const RELATION_MAP = {
js: [
"libraries/jsplumb.js",
@@ -119,7 +117,6 @@ export default {
CKEDITOR,
CODE_MIRROR,
ESLINT,
COMMONMARK,
RELATION_MAP,
PRINT_THIS,
CALENDAR_WIDGET,
@@ -4,6 +4,7 @@ import utils from "../../services/utils.js";
import appContext from "../../components/app_context.js";
import BasicWidget from "../basic_widget.js";
import shortcutService from "../../services/shortcuts.js";
import server from "../../services/server.js";
const TPL = `
<div class="markdown-import-dialog modal fade mx-auto" tabindex="-1" role="dialog">
@@ -46,18 +47,14 @@ export default class MarkdownImportDialog extends BasicWidget {
shortcutService.bindElShortcut(this.$widget, 'ctrl+return', () => this.sendForm());
}
async convertMarkdownToHtml(text) {
await libraryLoader.requireLibrary(libraryLoader.COMMONMARK);
async convertMarkdownToHtml(markdownContent) {
const {htmlContent} = await server.post('other/render-markdown', { markdownContent });
const reader = new commonmark.Parser();
const writer = new commonmark.HtmlRenderer();
const parsed = reader.parse(text);
const result = writer.render(parsed);
console.log(htmlContent);
const textEditor = await appContext.tabManager.getActiveContext().getTextEditor();
const viewFragment = textEditor.data.processor.toView(result);
const viewFragment = textEditor.data.processor.toView(htmlContent);
const modelFragment = textEditor.data.toModel(viewFragment);
textEditor.model.insertContent(modelFragment, textEditor.model.document.selection);
+11 -1
View File
@@ -1,4 +1,5 @@
const becca = require("../../becca/becca");
const markdownService = require("../../services/import/markdown");
function getIconUsage() {
const iconClassToCountMap = {};
@@ -24,6 +25,15 @@ function getIconUsage() {
return { iconClassToCountMap };
}
function renderMarkdown(req) {
const { markdownContent } = req.body;
return {
htmlContent: markdownService.renderToHtml(markdownContent, '')
};
}
module.exports = {
getIconUsage
getIconUsage,
renderMarkdown
};
+1
View File
@@ -324,6 +324,7 @@ function register(app) {
apiRoute(PST, '/api/delete-notes-preview', notesApiRoute.getDeleteNotesPreview);
route(GET, '/api/fonts', [auth.checkApiAuthOrElectron], fontsRoute.getFontCss);
apiRoute(GET, '/api/other/icon-usage', otherRoute.getIconUsage);
apiRoute(PST, '/api/other/render-markdown', otherRoute.renderMarkdown);
apiRoute(GET, '/api/recent-changes/:ancestorNoteId', recentChangesApiRoute.getRecentChanges);
apiRoute(GET, '/api/edited-notes/:date', revisionsApiRoute.getEditedNotesOnDate);
+18
View File
@@ -0,0 +1,18 @@
"use strict";
const marked = require("marked");
const htmlSanitizer = require("../html_sanitizer");
const importUtils = require("./utils");
function renderToHtml(content, title) {
const html = marked.parse(content, {
mangle: false,
headerIds: false
});
const h1Handled = importUtils.handleH1(html, title); // h1 handling needs to come before sanitization
return htmlSanitizer.sanitize(h1Handled);
}
module.exports = {
renderToHtml
};
+4 -24
View File
@@ -3,9 +3,10 @@
const noteService = require('../../services/notes');
const imageService = require('../../services/image');
const protectedSessionService = require('../protected_session');
const commonmark = require('commonmark');
const markdownService = require('./markdown');
const mimeService = require('./mime');
const utils = require('../../services/utils');
const importUtils = require('./utils');
const htmlSanitizer = require('../html_sanitizer');
function importSingleFile(taskContext, file, parentNote) {
@@ -120,16 +121,7 @@ function importMarkdown(taskContext, file, parentNote) {
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
const markdownContent = file.buffer.toString("utf-8");
const reader = new commonmark.Parser();
const writer = new commonmark.HtmlRenderer();
const parsed = reader.parse(markdownContent);
let htmlContent = writer.render(parsed);
htmlContent = htmlSanitizer.sanitize(htmlContent);
htmlContent = handleH1(htmlContent, title);
const htmlContent = markdownService.renderToHtml(markdownContent, title);
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
@@ -145,24 +137,12 @@ function importMarkdown(taskContext, file, parentNote) {
return note;
}
function handleH1(content, title) {
content = content.replace(/<h1>([^<]*)<\/h1>/gi, (match, text) => {
if (title.trim() === text.trim()) {
return ""; // remove whole H1 tag
} else {
return `<h2>${text}</h2>`;
}
});
return content;
}
function importHtml(taskContext, file, parentNote) {
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
let content = file.buffer.toString("utf-8");
content = htmlSanitizer.sanitize(content);
content = handleH1(content, title);
content = importUtils.handleH1(content, title);
const {note} = noteService.createNewNote({
parentNoteId: parentNote.noteId,
+16
View File
@@ -0,0 +1,16 @@
"use strict";
function handleH1(content, title) {
content = content.replace(/<h1>([^<]*)<\/h1>/gi, (match, text) => {
if (title.trim() === text.trim()) {
return ""; // remove whole H1 tag
} else {
return `<h2>${text}</h2>`;
}
});
return content;
}
module.exports = {
handleH1
};
+2 -5
View File
@@ -7,7 +7,6 @@ const noteService = require('../../services/notes');
const attributeService = require('../../services/attributes');
const BBranch = require('../../becca/entities/bbranch');
const path = require('path');
const commonmark = require('commonmark');
const protectedSessionService = require('../protected_session');
const mimeService = require("./mime");
const treeService = require("../tree");
@@ -15,6 +14,7 @@ const yauzl = require("yauzl");
const htmlSanitizer = require('../html_sanitizer');
const becca = require("../../becca/becca");
const BAttachment = require("../../becca/entities/battachment");
const markdownService = require("./markdown");
/**
* @param {TaskContext} taskContext
@@ -31,8 +31,6 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
// path => noteId, used only when meta file is not available
/** @type {Object.<string, string>} path => noteId | attachmentId */
const createdPaths = { '/': importRootNote.noteId, '\\': importRootNote.noteId };
const mdReader = new commonmark.Parser();
const mdWriter = new commonmark.HtmlRenderer();
let metaFile = null;
/** @type {BNote} */
let firstNote = null;
@@ -414,8 +412,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
function processNoteContent(noteMeta, type, mime, content, noteTitle, filePath) {
if (noteMeta?.format === 'markdown'
|| (!noteMeta && taskContext.data.textImportedAsText && ['text/markdown', 'text/x-markdown'].includes(mime))) {
const parsed = mdReader.parse(content);
content = mdWriter.render(parsed);
content = markdownService.renderToHtml(content, noteTitle);
}
if (type === 'text') {