mirror of https://github.com/laurent22/joplin.git
Desktop: Add monospace enforcement for certain elements in Markdown editor (#4689)
parent
e2db02887c
commit
81b3ddf0e7
|
@ -478,7 +478,7 @@ class Application extends BaseApplication {
|
|||
updateEditorFont() {
|
||||
const fontFamilies = [];
|
||||
if (Setting.value('style.editor.fontFamily')) fontFamilies.push(`"${Setting.value('style.editor.fontFamily')}"`);
|
||||
fontFamilies.push('monospace');
|
||||
fontFamilies.push('Avenir, Arial, sans-serif');
|
||||
|
||||
// The '*' and '!important' parts are necessary to make sure Russian text is displayed properly
|
||||
// https://github.com/laurent22/joplin/issues/155
|
||||
|
|
|
@ -377,6 +377,9 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
|||
`.CodeMirror-selected {
|
||||
background: #6b6b6b !important;
|
||||
}` : '';
|
||||
const monospaceFonts = [];
|
||||
if (Setting.value('style.editor.monospaceFontFamily')) monospaceFonts.push(`"${Setting.value('style.editor.monospaceFontFamily')}"`);
|
||||
monospaceFonts.push('monospace');
|
||||
|
||||
const element = document.createElement('style');
|
||||
element.setAttribute('id', 'codemirrorStyle');
|
||||
|
@ -412,6 +415,11 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
|||
padding-right: 10px !important;
|
||||
}
|
||||
|
||||
/* This enforces monospace for certain elements (code, tables, etc.) */
|
||||
.cm-jn-monospace {
|
||||
font-family: ${monospaceFonts.join(', ')} !important;
|
||||
}
|
||||
|
||||
.cm-header-1 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
import 'codemirror/addon/mode/multiplex';
|
||||
import 'codemirror/mode/stex/stex';
|
||||
import MarkdownUtils from '@joplin/lib/markdownUtils';
|
||||
import Setting from '@joplin/lib/models/Setting';
|
||||
|
||||
interface JoplinModeState {
|
||||
outer: any;
|
||||
openCharacter: string;
|
||||
inTable: boolean;
|
||||
inner: any;
|
||||
}
|
||||
|
||||
|
||||
// Joplin markdown is a the same as markdown mode, but it has configured defaults
|
||||
// and support for katex math blocks
|
||||
export default function useJoplinMode(CodeMirror: any) {
|
||||
|
@ -34,25 +43,29 @@ export default function useJoplinMode(CodeMirror: any) {
|
|||
}
|
||||
|
||||
return {
|
||||
startState: function(): { outer: any; openCharacter: string; inner: any } {
|
||||
startState: function(): JoplinModeState {
|
||||
return {
|
||||
outer: CodeMirror.startState(markdownMode),
|
||||
openCharacter: '',
|
||||
inTable: false,
|
||||
inner: CodeMirror.startState(stex),
|
||||
};
|
||||
},
|
||||
|
||||
copyState: function(state: any) {
|
||||
copyState: function(state: JoplinModeState) {
|
||||
return {
|
||||
outer: CodeMirror.copyState(markdownMode, state.outer),
|
||||
openCharacter: state.openCharacter,
|
||||
inTable: state.inTable,
|
||||
inner: CodeMirror.copyState(stex, state.inner),
|
||||
};
|
||||
},
|
||||
|
||||
token: function(stream: any, state: any) {
|
||||
token: function(stream: any, state: JoplinModeState) {
|
||||
let currentMode = markdownMode;
|
||||
let currentState = state.outer;
|
||||
|
||||
// //////// KATEX //////////
|
||||
let tokenLabel = 'katex-marker-open';
|
||||
let nextTokenPos = stream.string.length;
|
||||
let closing = false;
|
||||
|
@ -86,34 +99,75 @@ export default function useJoplinMode(CodeMirror: any) {
|
|||
|
||||
return tokenLabel;
|
||||
}
|
||||
// //////// End KATEX //////////
|
||||
|
||||
// //////// Markdown //////////
|
||||
// If we found a token in this stream but haven;t reached it yet, then we will
|
||||
// pass all the characters leading up to our token to markdown mode
|
||||
const oldString = stream.string;
|
||||
|
||||
stream.string = oldString.slice(0, nextTokenPos);
|
||||
const token = currentMode.token(stream, currentState);
|
||||
let token = currentMode.token(stream, currentState);
|
||||
stream.string = oldString;
|
||||
// //////// End Markdown //////////
|
||||
|
||||
// //////// Monospace //////////
|
||||
let isMonospace = false;
|
||||
// After being passed to the markdown mode we can check if the
|
||||
// code state variables are set
|
||||
// Code Block
|
||||
if (state.outer.code || (state.outer.thisLine && state.outer.thisLine.fencedCodeEnd)) {
|
||||
isMonospace = true;
|
||||
}
|
||||
// Indented Code
|
||||
if (state.outer.indentedCode) {
|
||||
isMonospace = true;
|
||||
}
|
||||
// Task lists
|
||||
if (state.outer.taskList || state.outer.taskOpen || state.outer.taskClosed) {
|
||||
isMonospace = true;
|
||||
}
|
||||
|
||||
// Any line that contains a | is potentially a table row
|
||||
if (stream.string.match(/\|/g)) {
|
||||
// Check if the current and following line together make a valid
|
||||
// markdown table header
|
||||
if (MarkdownUtils.matchingTableDivider(stream.string, stream.lookAhead(1))) {
|
||||
state.inTable = true;
|
||||
}
|
||||
|
||||
// Treat all lines that start with | as a table row
|
||||
if (state.inTable || stream.string[0] === '|') {
|
||||
isMonospace = true;
|
||||
}
|
||||
} else {
|
||||
state.inTable = false;
|
||||
}
|
||||
|
||||
if (isMonospace) { token = `${token} jn-monospace`; }
|
||||
// //////// End Monospace //////////
|
||||
|
||||
return token;
|
||||
},
|
||||
|
||||
indent: function(state: any, textAfter: string, line: any) {
|
||||
indent: function(state: JoplinModeState, textAfter: string, line: any) {
|
||||
const mode = state.openCharacter ? stex : markdownMode;
|
||||
if (!mode.indent) return CodeMirror.Pass;
|
||||
return mode.indent(state.openCharacter ? state.inner : state.outer, textAfter, line);
|
||||
},
|
||||
|
||||
blankLine: function(state: any) {
|
||||
blankLine: function(state: JoplinModeState) {
|
||||
const mode = state.openCharacter ? stex : markdownMode;
|
||||
if (mode.blankLine) {
|
||||
mode.blankLine(state.openCharacter ? state.inner : state.outer);
|
||||
}
|
||||
|
||||
state.inTable = false;
|
||||
},
|
||||
|
||||
electricChars: markdownMode.electricChars,
|
||||
|
||||
innerMode: function(state: any) {
|
||||
innerMode: function(state: JoplinModeState) {
|
||||
return state.openCharacter ? { state: state.inner, mode: stex } : { state: state.outer, mode: markdownMode };
|
||||
},
|
||||
|
||||
|
|
|
@ -137,6 +137,30 @@ const markdownUtils = {
|
|||
return output.join('\n');
|
||||
},
|
||||
|
||||
countTableColumns(line: string) {
|
||||
if (!line) return 0;
|
||||
|
||||
const trimmed = line.trim();
|
||||
let pipes = (line.match(/\|/g) || []).length;
|
||||
|
||||
if (trimmed[0] === '|') { pipes -= 1; }
|
||||
if (trimmed[trimmed.length - 1] === '|') { pipes -= 1; }
|
||||
|
||||
return pipes + 1;
|
||||
},
|
||||
|
||||
matchingTableDivider(header: string, divider: string) {
|
||||
if (!header || !divider) return false;
|
||||
|
||||
const invalidChars = divider.match(/[^\s\-:|]/g);
|
||||
|
||||
if (invalidChars) { return false; }
|
||||
|
||||
const columns = markdownUtils.countTableColumns(header);
|
||||
const cols = markdownUtils.countTableColumns(divider);
|
||||
return cols > 0 && (cols >= columns);
|
||||
},
|
||||
|
||||
titleFromBody(body: string) {
|
||||
if (!body) return '';
|
||||
const mdLinkRegex = /!?\[([^\]]+?)\]\(.+?\)/g;
|
||||
|
|
|
@ -866,6 +866,17 @@ class Setting extends BaseModel {
|
|||
appTypes: ['desktop'],
|
||||
section: 'appearance',
|
||||
label: () => _('Editor font family'),
|
||||
description: () =>
|
||||
_('If the font is incorrect or empty, it will default to a generic monospace font.'),
|
||||
storage: SettingStorage.File,
|
||||
},
|
||||
'style.editor.monospaceFontFamily': {
|
||||
value: '',
|
||||
type: SettingItemType.String,
|
||||
public: true,
|
||||
appTypes: ['desktop'],
|
||||
section: 'appearance',
|
||||
label: () => _('Editor monospace font family'),
|
||||
description: () =>
|
||||
_('This should be a *monospace* font or some elements will render incorrectly. If the font ' +
|
||||
'is incorrect or empty, it will default to a generic monospace font.'),
|
||||
|
|
Loading…
Reference in New Issue