1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-11 18:24:43 +02:00

Desktop: WYSIWYG: Added support for code blocks edition and creation

This commit is contained in:
Laurent Cozic 2020-04-09 17:47:12 +01:00
parent 7ccd19e21d
commit ec499eecd5
8 changed files with 93 additions and 19 deletions

View File

@ -1,4 +1,4 @@
<div class="joplin-editable"><pre class="joplin-source" data-joplin-source-open="```javascript&#10;" data-joplin-source-close="&#10;```">function() {
<div class="joplin-editable"><pre class="joplin-source" data-joplin-language="javascript" data-joplin-source-open="```javascript&#10;" data-joplin-source-close="&#10;```">function() {
console.info('bonjour');
}</pre><pre class="hljs"><code><span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
<span class="hljs-built_in">console</span>.info(<span class="hljs-string">'bonjour'</span>);

View File

@ -1 +1 @@
<div class="joplin-editable"><pre class="joplin-source" data-joplin-source-open="```html&#10;" data-joplin-source-close="&#10;```">&lt;a href=&quot;#&quot; onclick=&quot;leavethisalone&quot;&gt;testing fence&lt;/a&gt;</pre><pre class="hljs"><code><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"leavethisalone"</span>&gt;</span>testing fence<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></code></pre></div>
<div class="joplin-editable"><pre class="joplin-source" data-joplin-language="html" data-joplin-source-open="```html&#10;" data-joplin-source-close="&#10;```">&lt;a href=&quot;#&quot; onclick=&quot;leavethisalone&quot;&gt;testing fence&lt;/a&gt;</pre><pre class="hljs"><code><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"leavethisalone"</span>&gt;</span>testing fence<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></code></pre></div>

View File

@ -34,6 +34,19 @@ function findBlockSource(node:any) {
closeCharacters: source.getAttribute('data-joplin-source-close'),
content: source.textContent,
node: source,
language: source.getAttribute('data-joplin-language') || '',
};
}
function newBlockSource(language:string = '', content:string = ''):any {
const fence = language === 'katex' ? '$$' : '```';
return {
openCharacters: `\n${fence}${language}\n`,
closeCharacters: `\n${fence}\n`,
content: content,
node: null,
language: language,
};
}
@ -309,8 +322,13 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => {
.tox-editor-header .tox-toolbar__primary,
.tox .tox-toolbar-overlord,
.tox.tox-tinymce-aux .tox-toolbar__overflow,
.tox .tox-statusbar {
background-color: ${theme.backgroundColor};
.tox .tox-statusbar,
.tox .tox-dialog__header,
.tox .tox-dialog,
.tox textarea,
.tox input,
.tox .tox-dialog__footer {
background-color: ${theme.backgroundColor} !important;
}
.tox .tox-editor-header {
@ -318,8 +336,15 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => {
}
.tox .tox-tbtn,
.tox .tox-tbtn svg {
color: ${theme.color};
.tox .tox-tbtn svg,
.tox .tox-dialog__header,
.tox .tox-button--icon .tox-icon svg,
.tox .tox-button.tox-button--icon .tox-icon svg,
.tox textarea,
.tox input,
.tox .tox-label,
.tox .tox-toolbar-label {
color: ${theme.color} !important;
fill: ${theme.color} !important;
}
@ -337,6 +362,10 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => {
background-color: ${theme.selectedColor};
}
.tox .tox-button--naked:hover:not(:disabled) {
background-color: ${theme.backgroundColor} !important;
}
.tox .tox-tbtn:hover {
background-color: ${theme.backgroundHover};
color: ${theme.colorHover};
@ -350,7 +379,8 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => {
.tox-tinymce,
.tox .tox-toolbar__group,
.tox.tox-tinymce-aux .tox-toolbar__overflow {
.tox.tox-tinymce-aux .tox-toolbar__overflow,
.tox .tox-dialog__footer {
border-color: ${theme.dividerColor} !important;
}
`));
@ -389,27 +419,34 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => {
valid_elements: '*[*]', // We already filter in sanitize_html
menubar: false,
branding: false,
toolbar: 'bold italic | link codeformat codesample joplinAttach | numlist bullist joplinChecklist | h1 h2 h3 hr blockquote',
toolbar: 'bold italic | link joplinInlineCode joplinCodeBlock joplinAttach | numlist bullist joplinChecklist | h1 h2 h3 hr blockquote',
setup: (editor:any) => {
function openEditDialog(editable:any) {
const source = findBlockSource(editable);
const source = editable ? findBlockSource(editable) : newBlockSource();
editor.windowManager.open({
title: 'Edit',
size: 'large',
initialData: {
codeTextArea: source.content,
languageInput: source.language,
},
onSubmit: async (dialogApi:any) => {
const newSource = dialogApi.getData().codeTextArea;
const md = `${source.openCharacters}${newSource.trim()}${source.closeCharacters}`;
const newSource = newBlockSource(dialogApi.getData().languageInput, dialogApi.getData().codeTextArea);
const md = `${newSource.openCharacters}${newSource.content.trim()}${newSource.closeCharacters}`;
const result = await markupToHtml.current(MarkupToHtml.MARKUP_LANGUAGE_MARKDOWN, md, { bodyOnly: true });
// markupToHtml will return the complete editable HTML, but we only
// want to update the inner HTML, so as not to break additional props that
// are added by TinyMCE on the main node.
editable.innerHTML = editableInnerHtml(result.html);
if (editable) {
editable.innerHTML = editableInnerHtml(result.html);
} else {
editor.insertContent(result.html);
}
dialogApi.close();
editor.fire('joplinChange');
dispatchDidUpdate(editor);
@ -420,6 +457,14 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => {
body: {
type: 'panel',
items: [
{
type: 'input',
name: 'languageInput',
label: 'Language',
// Katex is a special case with special opening/closing tags
// and we don't currently handle switching the language in this case.
disabled: source.language === 'katex',
},
{
type: 'textarea',
name: 'codeTextArea',
@ -459,6 +504,30 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => {
},
});
editor.ui.registry.addButton('joplinCodeBlock', {
tooltip: 'Code Block',
icon: 'code-sample',
onAction: async function() {
openEditDialog(null);
},
});
editor.ui.registry.addToggleButton('joplinInlineCode', {
tooltip: 'Inline Code',
icon: 'sourcecode',
onAction: function() {
editor.execCommand('mceToggleFormat', false, 'code', { class: 'inline-code' });
},
onSetup: function(api:any) {
api.setActive(editor.formatter.match('code'));
const unbind = editor.formatter.formatChanged('code', api.setActive).unbind;
return function() {
if (unbind) unbind();
};
},
});
// TODO: remove event on unmount?
editor.on('DblClick', (event:any) => {
const editable = findEditableContainer(event.target);
@ -489,7 +558,10 @@ const TinyMCE = (props:TinyMCEProps, ref:any) => {
// -----------------------------------------------------------------------------------------
const loadDocumentAssets = (editor:any, pluginAssets:any[]) => {
const cssFiles = ['css/fork-awesome.min.css'].concat(
const cssFiles = [
'css/fork-awesome.min.css',
`gui/note-viewer/pluginAssets/highlight.js/${theme.codeThemeCss}`,
].concat(
pluginAssets
.filter((a:any) => a.mime === 'text/css')
.map((a:any) => a.path)

View File

@ -221,7 +221,7 @@ class MdToHtml {
// The strings includes the last \n that is part of the fence,
// so we remove it because we need the exact code in the source block
const trimmedStr = str.replace(/(.*)\n$/, '$1');
const sourceBlockHtml = `<pre class="joplin-source" data-joplin-source-open="\`\`\`${lang}&#10;" data-joplin-source-close="&#10;\`\`\`">${markdownIt.utils.escapeHtml(trimmedStr)}</pre>`;
const sourceBlockHtml = `<pre class="joplin-source" data-joplin-language="${lang}" data-joplin-source-open="\`\`\`${lang}&#10;" data-joplin-source-close="&#10;\`\`\`">${markdownIt.utils.escapeHtml(trimmedStr)}</pre>`;
try {
let hlCode = '';

View File

@ -107,7 +107,7 @@ function renderFountainScript(markdownIt, content) {
return `
<div class="fountain joplin-editable">
<pre class="joplin-source" data-joplin-source-open="\`\`\`fountain&#10;" data-joplin-source-close="&#10;\`\`\`&#10;">${markdownIt.utils.escapeHtml(content)}</pre>
<pre class="joplin-source" data-joplin-language="fountain" data-joplin-source-open="\`\`\`fountain&#10;" data-joplin-source-close="&#10;\`\`\`&#10;">${markdownIt.utils.escapeHtml(content)}</pre>
<div class="title-page">
${result.html.title_page}
</div>

View File

@ -230,7 +230,7 @@ module.exports = {
const katexInline = function(latex) {
options.displayMode = false;
try {
return `<span class="joplin-editable"><span class="joplin-source" data-joplin-source-open="$" data-joplin-source-close="$">${latex}</span>${renderToStringWithCache(latex, options)}</span>`;
return `<span class="joplin-editable"><span class="joplin-source" data-joplin-language="katex" data-joplin-source-open="$" data-joplin-source-close="$">${latex}</span>${renderToStringWithCache(latex, options)}</span>`;
} catch (error) {
console.error('Katex error for:', latex, error);
return latex;
@ -245,7 +245,7 @@ module.exports = {
const katexBlock = function(latex) {
options.displayMode = true;
try {
return `<div class="joplin-editable"><pre class="joplin-source" data-joplin-source-open="$$&#10;" data-joplin-source-close="&#10;$$&#10;">${latex}</pre>${renderToStringWithCache(latex, options)}</div>`;
return `<div class="joplin-editable"><pre class="joplin-source" data-joplin-language="katex" data-joplin-source-open="$$&#10;" data-joplin-source-close="&#10;$$&#10;">${latex}</pre>${renderToStringWithCache(latex, options)}</div>`;
} catch (error) {
console.error('Katex error for:', latex, error);
return latex;

View File

@ -32,7 +32,7 @@ function installRule(markdownIt:any, mdOptions:any, ruleOptions:any, context:any
const contentHtml = markdownIt.utils.escapeHtml(token.content);
return `
<div class="joplin-editable">
<pre class="joplin-source" data-joplin-source-open="\`\`\`mermaid&#10;" data-joplin-source-close="&#10;\`\`\`&#10;">${contentHtml}</pre>
<pre class="joplin-source" data-joplin-language="mermaid" data-joplin-source-open="\`\`\`mermaid&#10;" data-joplin-source-close="&#10;\`\`\`&#10;">${contentHtml}</pre>
<div class="mermaid">${contentHtml}</div>
</div>
`;

View File

@ -222,7 +222,9 @@ module.exports = function(theme) {
max-width: 100%;
height: auto;
}
.inline-code {
.inline-code,
.tox :not(.joplin-editable) code {
border: 1px solid ${theme.htmlCodeBorderColor};
background-color: ${theme.htmlCodeBackgroundColor};
padding-right: .2em;