mirror of
https://github.com/sparksuite/simplemde-markdown-editor.git
synced 2024-06-08 00:42:31 +02:00
Context aware code button to handle toggling on and off code blocks and inline code markup
This commit is contained in:
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…
Reference in a new issue