Context aware code button to handle toggling on and off code blocks and inline code markup

pull/228/head
Dave Brondsema 8 years ago
parent 99e4cba72e
commit bce5372947

@ -75,6 +75,7 @@ simplemde.value("This text will appear in the editor");
- **uniqueId**: You must set a unique string identifier so that SimpleMDE can autosave. Something that separates this from other instances of SimpleMDE elsewhere on your website.
- **blockStyles**: Customize how certain buttons that style blocks of text behave.
- **bold** Can be set to `**` or `__`. Defaults to `**`.
- **code** Can be set to ```` ``` ```` or `~~~`. Defaults to ```` ``` ````.
- **italic** Can be set to `*` or `_`. Defaults to `*`.
- **element**: The DOM element for the textarea to use. Defaults to the first textarea on the page.
- **hideIcons**: An array of icon names to hide. Can be used to hide specific icons shown by default without completely customizing the toolbar.

@ -253,7 +253,224 @@ function toggleStrikethrough(editor) {
* Action for toggling code block.
*/
function toggleCodeBlock(editor) {
_toggleBlock(editor, "code", "```\r\n", "\r\n```");
var fenceCharsToInsert = editor.options.blockStyles.code;
function fencing_line(line) {
/* return true, if this is a ``` or ~~~ line */
if(typeof line !== "object") {
throw "fencing_line() takes a 'line' object (not a line number, or line text). Got: " + typeof line + ": " + line;
}
return line.styles && line.styles[2] && line.styles[2].indexOf("formatting-code-block") !== -1;
}
function code_type(cm, line_num, line, firstTok, lastTok) {
/*
* Return "single", "indented", "fenced" or false
*
* cm and line_num are required. Others are optional for efficiency
* To check in the middle of a line, pass in firstTok yourself.
*/
line = line || cm.getLineHandle(line_num);
firstTok = firstTok || cm.getTokenAt({
line: line_num,
ch: 1
});
lastTok = lastTok || (!!line.text && cm.getTokenAt({
line: line_num,
ch: line.text.length - 1
}));
var types = firstTok.type ? firstTok.type.split(" ") : [];
if(lastTok && lastTok.state.base.indentedCode) {
// have to check last char, since first chars of first line aren"t marked as indented
return "indented";
} else if(types.indexOf("comment") === -1) {
// has to be after "indented" check, since first chars of first indented line aren"t marked as such
return false;
} else if(firstTok.state.base.fencedChars || lastTok.state.base.fencedChars || fencing_line(line)) {
return "fenced";
} else {
return "single";
}
}
function insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert) {
var start_line_sel = cur_start.line + 1,
end_line_sel = cur_end.line + 1,
sel_multi = cur_start.line !== cur_end.line,
repl_start = fenceCharsToInsert + "\n",
repl_end = "\n" + fenceCharsToInsert;
if(sel_multi) {
end_line_sel++;
}
// handle last char including \n or not
if(sel_multi && cur_end.ch === 0) {
repl_end = fenceCharsToInsert + "\n";
end_line_sel--;
}
_replaceSelection(cm, false, [repl_start, repl_end]);
cm.setSelection({
line: start_line_sel,
ch: 0
}, {
line: end_line_sel,
ch: 0
});
}
var cm = editor.codemirror,
cur_start = cm.getCursor("start"),
cur_end = cm.getCursor("end"),
tok = cm.getTokenAt({
line: cur_start.line,
ch: cur_start.ch || 1
}), // avoid ch 0 which is a cursor pos but not token
line = cm.getLineHandle(cur_start.line),
is_code = code_type(cm, cur_start.line, line, tok);
var block_start, block_end, lineCount;
if(is_code === "single") {
// similar to some SimpleMDE _toggleBlock logic
var start = line.text.slice(0, cur_start.ch).replace("`", ""),
end = line.text.slice(cur_start.ch).replace("`", "");
cm.replaceRange(start + end, {
line: cur_start.line,
ch: 0
}, {
line: cur_start.line,
ch: 99999999999999
});
cur_start.ch--;
if(cur_start !== cur_end) {
cur_end.ch--;
}
cm.setSelection(cur_start, cur_end);
cm.focus();
} else if(is_code === "fenced") {
if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
// use selection
for(block_start = cur_start.line; block_start >= 0; block_start--) {
line = cm.getLineHandle(block_start);
if(fencing_line(line)) {
break;
}
}
var fencedTok = cm.getTokenAt({
line: block_start,
ch: 1
});
insertFencingAtSelection(cm, cur_start, cur_end, fencedTok.state.base.fencedChars);
} else {
// no selection, search for ends of this fenced block
var search_from = cur_start.line;
if(fencing_line(cm.getLineHandle(cur_start.line))) { // gets a little tricky if cursor is right on a fenced line
if(code_type(cm, cur_start.line + 1) === "fenced") {
block_start = cur_start.line;
search_from = cur_start.line + 1; // for searching for "end"
} else {
block_end = cur_start.line;
search_from = cur_start.line - 1; // for searching for "start"
}
}
if(block_start === undefined) {
for(block_start = search_from; block_start >= 0; block_start--) {
line = cm.getLineHandle(block_start);
if(fencing_line(line)) {
break;
}
}
}
if(block_end === undefined) {
lineCount = cm.lineCount();
for(block_end = search_from; block_end < lineCount; block_end++) {
line = cm.getLineHandle(block_end);
if(fencing_line(line)) {
break;
}
}
}
cm.operation(function() {
cm.replaceRange("", {
line: block_start,
ch: 0
}, {
line: block_start + 1,
ch: 0
});
cm.replaceRange("", {
line: block_end - 1,
ch: 0
}, {
line: block_end,
ch: 0
});
});
cm.focus();
}
} else if(is_code === "indented") {
if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
// use selection
block_start = cur_start.line;
block_end = cur_end.line;
if(cur_end.ch === 0) {
block_end--;
}
} else {
// no selection, search for ends of this indented block
for(block_start = cur_start.line; block_start >= 0; block_start--) {
line = cm.getLineHandle(block_start);
if(line.text.match(/^\s*$/)) {
// empty or all whitespace - keep going
continue;
} else {
if(code_type(cm, block_start, line) !== "indented") {
block_start += 1;
break;
}
}
}
lineCount = cm.lineCount();
for(block_end = cur_start.line; block_end < lineCount; block_end++) {
line = cm.getLineHandle(block_end);
if(line.text.match(/^\s*$/)) {
// empty or all whitespace - keep going
continue;
} else {
if(code_type(cm, block_end, line) !== "indented") {
block_end -= 1;
break;
}
}
}
}
// if we are going to un-indent based on a selected set of lines, and the next line is indented too, we need to
// insert a blank line so that the next line(s) continue to be indented code
var next_line = cm.getLineHandle(block_end + 1),
next_line_last_tok = next_line && cm.getTokenAt({
line: block_end + 1,
ch: next_line.text.length - 1
}),
next_line_indented = next_line_last_tok && next_line_last_tok.state.base.indentedCode;
if(next_line_indented) {
cm.replaceRange("\n", {
line: block_end + 1,
ch: 0
});
}
for(var i = block_start; i <= block_end; i++) {
cm.indentLine(i, "subtract"); // TODO: this doesn't get tracked in the history, so can't be undone :(
}
cm.focus();
} else {
// insert code formatting
var no_sel_and_starting_of_line = (cur_start.line === cur_end.line && cur_start.ch === cur_end.ch && cur_start.ch === 0);
var sel_multi = cur_start.line !== cur_end.line;
if(no_sel_and_starting_of_line || sel_multi) {
insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert);
} else {
_replaceSelection(cm, false, ["`", "`"]);
}
}
}
/**
@ -949,6 +1166,7 @@ var insertTexts = {
var blockStyles = {
"bold": "**",
"code": "```",
"italic": "*"
};
@ -1038,7 +1256,9 @@ function SimpleMDE(options) {
// Set default options for parsing config
options.parsingConfig = options.parsingConfig || {};
options.parsingConfig = extend({
highlightFormatting: true // needed for toggleCodeBlock to detect types of code
}, options.parsingConfig || {});
// Merging the insertTexts, with the given options

Loading…
Cancel
Save