Skip to content

Commit e808416

Browse files
committed
FEATURE: custom inline and block math delimiters
1 parent 5233654 commit e808416

File tree

4 files changed

+55
-36
lines changed

4 files changed

+55
-36
lines changed

assets/javascripts/lib/discourse-markdown/discourse-math.js.es6

+42-35
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ function isSafeBoundary(character_code, delimiter_code, md) {
2323
return false;
2424
}
2525

26-
function math_input(state, silent, delimiter_code) {
26+
let inlineMath = delimiter => (state, silent) => {
27+
let delimiter_code = delimiter.charCodeAt(0);
2728
let pos = state.pos,
2829
posMax = state.posMax;
2930

@@ -71,35 +72,20 @@ function math_input(state, silent, delimiter_code) {
7172
let token = state.push("html_raw", "", 0);
7273

7374
const escaped = state.md.utils.escapeHtml(data);
74-
let math_class = delimiter_code === 36 ? "'math'" : "'asciimath'";
75+
let math_class = delimiter_code === 37 /* % */ ? "'asciimath'" : "'math'";
7576
token.content = `<span class=${math_class}>${escaped}</span>`;
7677
state.pos = found + 1;
7778
return true;
7879
}
7980

80-
function inlineMath(state, silent) {
81-
return math_input(state, silent, 36 /* $ */);
82-
}
83-
84-
function asciiMath(state, silent) {
85-
return math_input(state, silent, 37 /* % */);
86-
}
87-
88-
function isBlockMarker(state, start, max, md) {
89-
if (state.src.charCodeAt(start) !== 36 /* $ */) {
90-
return false;
91-
}
81+
function isBlockMarker(state, start, max, md, blockMarker) {
9282

93-
start++;
94-
95-
if (state.src.charCodeAt(start) !== 36 /* $ */) {
83+
if (!state.src.startsWith(blockMarker, start)) {
9684
return false;
9785
}
9886

99-
start++;
100-
101-
// ensure we only have newlines after our $$
102-
for (let i = start; i < max; i++) {
87+
// ensure we only have spaces and newline after blockmarker
88+
for (let i = start + blockMarker.length; i < max; i++) {
10389
if (!md.utils.isSpace(state.src.charCodeAt(i))) {
10490
return false;
10591
}
@@ -108,11 +94,11 @@ function isBlockMarker(state, start, max, md) {
10894
return true;
10995
}
11096

111-
function blockMath(state, startLine, endLine, silent) {
97+
let blockMath = blockMarker => (state, startLine, endLine, silent) => {
11298
let start = state.bMarks[startLine] + state.tShift[startLine],
11399
max = state.eMarks[startLine];
114100

115-
if (!isBlockMarker(state, start, max, state.md)) {
101+
if (!isBlockMarker(state, start, max, state.md, blockMarker)) {
116102
return false;
117103
}
118104

@@ -125,7 +111,7 @@ function blockMath(state, startLine, endLine, silent) {
125111
for (;;) {
126112
nextLine++;
127113

128-
// unclosed $$ is considered math
114+
// Unclosed blockmarker is considered math
129115
if (nextLine >= endLine) {
130116
break;
131117
}
@@ -135,7 +121,8 @@ function blockMath(state, startLine, endLine, silent) {
135121
state,
136122
state.bMarks[nextLine] + state.tShift[nextLine],
137123
state.eMarks[nextLine],
138-
state.md
124+
state.md,
125+
blockMarker.replace('\\begin{', '\\end{')
139126
)
140127
) {
141128
closed = true;
@@ -145,11 +132,15 @@ function blockMath(state, startLine, endLine, silent) {
145132

146133
let token = state.push("html_raw", "", 0);
147134

148-
let endContent = closed ? state.eMarks[nextLine - 1] : state.eMarks[nextLine];
149-
let content = state.src.slice(
150-
state.bMarks[startLine + 1] + state.tShift[startLine + 1],
151-
endContent
152-
);
135+
// Blockmarker starting with \begin{ end ending with '\end{'
136+
// needs to be passed to the TeX engine
137+
let endContent = blockMarker.startsWith('\\begin{') || !closed ?
138+
state.eMarks[nextLine] : state.eMarks[nextLine - 1];
139+
140+
let startContent = blockMarker.startsWith('\\begin{') ?
141+
state.bMarks[startLine] : state.bMarks[startLine + 1] + state.tShift[startLine + 1];
142+
143+
let content = state.src.slice(startContent, endContent);
153144

154145
const escaped = state.md.utils.escapeHtml(content);
155146
token.content = `<div class='math'>\n${escaped}\n</div>\n`;
@@ -165,18 +156,34 @@ export function setup(helper) {
165156
}
166157

167158
let enable_asciimath;
159+
let inlineDelimiters, blockDelimiters;
168160
helper.registerOptions((opts, siteSettings) => {
169161
opts.features.math = siteSettings.discourse_math_enabled;
170162
enable_asciimath = siteSettings.discourse_math_enable_asciimath;
163+
inlineDelimiters = siteSettings.discourse_math_inline_delimiters;
164+
blockDelimiters = siteSettings.discourse_math_block_delimiters;
171165
});
172166

173167
helper.registerPlugin(md => {
174168
if (enable_asciimath) {
175-
md.inline.ruler.after("escape", "asciimath", asciiMath);
169+
md.inline.ruler.after("escape", "asciimath", inlineMath('%'));
170+
}
171+
if (inlineDelimiters) {
172+
inlineDelimiters.split('|').forEach(delim => {
173+
// We expect only one character
174+
// for inline math delimiter
175+
let d = delim.trim();
176+
if (d.length !== 1) return;
177+
md.inline.ruler.after("escape", "math", inlineMath(d));
178+
});
179+
}
180+
if (blockDelimiters) {
181+
blockDelimiters.split('|').forEach(delim => {
182+
let d = delim.trim();
183+
md.block.ruler.after("code", "math", blockMath(delim), {
184+
alt: ["paragraph", "reference", "blockquote", "list"]
185+
});
186+
});
176187
}
177-
md.inline.ruler.after("escape", "math", inlineMath);
178-
md.block.ruler.after("code", "math", blockMath, {
179-
alt: ["paragraph", "reference", "blockquote", "list"]
180-
});
181188
});
182189
}

config/locales/server.en.yml

+2
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ en:
88
discourse_math_zoom_on_hover: 'Zoom 200% on hover (MathJax only)'
99
discourse_math_enable_accessibility: 'Enable accessibility features (MathJax only)'
1010
discourse_math_enable_asciimath: 'Enable asciimath (will add special processing to % delimited input) (MathJax only)'
11+
discourse_math_inline_delimiters: 'You can have multiple delimiters for inline math, but each must be single character'
12+
discourse_math_block_delimiters: 'You can have multiple delimiters for block math. Delimiters starting with "\begin{" will be passed to the LaTeX engine and are expected to end with the corresponding \end{} command'

config/settings.yml

+10
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ plugins:
99
choices:
1010
- mathjax
1111
- katex
12+
discourse_math_inline_delimiters:
13+
default: '$'
14+
client: true
15+
type: list
16+
list_type: compact
17+
discourse_math_block_delimiters:
18+
default: '$$|\\begin{align}'
19+
client: true
20+
type: list
21+
list_type: compact
1222
discourse_math_zoom_on_hover:
1323
default: false
1424
client: true

plugin.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22

33
# name: discourse-math
4-
# about: Official mathjax plugin for Discourse
4+
# about: Official LaTeX plugin for Discourse
55
# version: 0.9
66
# authors: Sam Saffron (sam)
77
# url: https://github.com/discourse/discourse-math

0 commit comments

Comments
 (0)