@@ -13139,7 +13139,286 @@ function toggleStrikethrough(editor) {
1313913139 * Action for toggling code block.
1314013140 */
1314113141function toggleCodeBlock(editor) {
13142- _toggleBlock(editor, "code", "```\r\n", "\r\n```");
13142+ var fenceCharsToInsert = editor.options.blockStyles.code;
13143+
13144+ function fencing_line(line) {
13145+ /* return true, if this is a ``` or ~~~ line */
13146+ if(typeof line !== "object") {
13147+ throw "fencing_line() takes a 'line' object (not a line number, or line text). Got: " + typeof line + ": " + line;
13148+ }
13149+ return line.styles && line.styles[2] && line.styles[2].indexOf("formatting-code-block") !== -1;
13150+ }
13151+
13152+ function token_state(token) {
13153+ // base goes an extra level deep when mode backdrops are used, e.g. spellchecker on
13154+ return token.state.base.base || token.state.base;
13155+ }
13156+
13157+ function code_type(cm, line_num, line, firstTok, lastTok) {
13158+ /*
13159+ * Return "single", "indented", "fenced" or false
13160+ *
13161+ * cm and line_num are required. Others are optional for efficiency
13162+ * To check in the middle of a line, pass in firstTok yourself.
13163+ */
13164+ line = line || cm.getLineHandle(line_num);
13165+ firstTok = firstTok || cm.getTokenAt({
13166+ line: line_num,
13167+ ch: 1
13168+ });
13169+ lastTok = lastTok || (!!line.text && cm.getTokenAt({
13170+ line: line_num,
13171+ ch: line.text.length - 1
13172+ }));
13173+ var types = firstTok.type ? firstTok.type.split(" ") : [];
13174+ if(lastTok && token_state(lastTok).indentedCode) {
13175+ // have to check last char, since first chars of first line aren"t marked as indented
13176+ return "indented";
13177+ } else if(types.indexOf("comment") === -1) {
13178+ // has to be after "indented" check, since first chars of first indented line aren"t marked as such
13179+ return false;
13180+ } else if(token_state(firstTok).fencedChars || token_state(lastTok).fencedChars || fencing_line(line)) {
13181+ return "fenced";
13182+ } else {
13183+ return "single";
13184+ }
13185+ }
13186+
13187+ function insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert) {
13188+ var start_line_sel = cur_start.line + 1,
13189+ end_line_sel = cur_end.line + 1,
13190+ sel_multi = cur_start.line !== cur_end.line,
13191+ repl_start = fenceCharsToInsert + "\n",
13192+ repl_end = "\n" + fenceCharsToInsert;
13193+ if(sel_multi) {
13194+ end_line_sel++;
13195+ }
13196+ // handle last char including \n or not
13197+ if(sel_multi && cur_end.ch === 0) {
13198+ repl_end = fenceCharsToInsert + "\n";
13199+ end_line_sel--;
13200+ }
13201+ _replaceSelection(cm, false, [repl_start, repl_end]);
13202+ cm.setSelection({
13203+ line: start_line_sel,
13204+ ch: 0
13205+ }, {
13206+ line: end_line_sel,
13207+ ch: 0
13208+ });
13209+ }
13210+
13211+ var cm = editor.codemirror,
13212+ cur_start = cm.getCursor("start"),
13213+ cur_end = cm.getCursor("end"),
13214+ tok = cm.getTokenAt({
13215+ line: cur_start.line,
13216+ ch: cur_start.ch || 1
13217+ }), // avoid ch 0 which is a cursor pos but not token
13218+ line = cm.getLineHandle(cur_start.line),
13219+ is_code = code_type(cm, cur_start.line, line, tok);
13220+ var block_start, block_end, lineCount;
13221+
13222+ if(is_code === "single") {
13223+ // similar to some SimpleMDE _toggleBlock logic
13224+ var start = line.text.slice(0, cur_start.ch).replace("`", ""),
13225+ end = line.text.slice(cur_start.ch).replace("`", "");
13226+ cm.replaceRange(start + end, {
13227+ line: cur_start.line,
13228+ ch: 0
13229+ }, {
13230+ line: cur_start.line,
13231+ ch: 99999999999999
13232+ });
13233+ cur_start.ch--;
13234+ if(cur_start !== cur_end) {
13235+ cur_end.ch--;
13236+ }
13237+ cm.setSelection(cur_start, cur_end);
13238+ cm.focus();
13239+ } else if(is_code === "fenced") {
13240+ if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
13241+ // use selection
13242+
13243+ // find the fenced line so we know what type it is (tilde, backticks, number of them)
13244+ for(block_start = cur_start.line; block_start >= 0; block_start--) {
13245+ line = cm.getLineHandle(block_start);
13246+ if(fencing_line(line)) {
13247+ break;
13248+ }
13249+ }
13250+ var fencedTok = cm.getTokenAt({
13251+ line: block_start,
13252+ ch: 1
13253+ });
13254+ var fence_chars = token_state(fencedTok).fencedChars;
13255+ var start_text, start_line;
13256+ var end_text, end_line;
13257+ // check for selection going up against fenced lines, in which case we don't want to add more fencing
13258+ if(fencing_line(cm.getLineHandle(cur_start.line))) {
13259+ start_text = "";
13260+ start_line = cur_start.line;
13261+ } else if(fencing_line(cm.getLineHandle(cur_start.line - 1))) {
13262+ start_text = "";
13263+ start_line = cur_start.line - 1;
13264+ } else {
13265+ start_text = fence_chars + "\n";
13266+ start_line = cur_start.line;
13267+ }
13268+ if(fencing_line(cm.getLineHandle(cur_end.line))) {
13269+ end_text = "";
13270+ end_line = cur_end.line;
13271+ if(cur_end.ch === 0) {
13272+ end_line += 1;
13273+ }
13274+ } else if(cur_end.ch !== 0 && fencing_line(cm.getLineHandle(cur_end.line + 1))) {
13275+ end_text = "";
13276+ end_line = cur_end.line + 1;
13277+ } else {
13278+ end_text = fence_chars + "\n";
13279+ end_line = cur_end.line + 1;
13280+ }
13281+ if(cur_end.ch === 0) {
13282+ // full last line selected, putting cursor at beginning of next
13283+ end_line -= 1;
13284+ }
13285+ cm.operation(function() {
13286+ // end line first, so that line numbers don't change
13287+ cm.replaceRange(end_text, {
13288+ line: end_line,
13289+ ch: 0
13290+ }, {
13291+ line: end_line + (end_text ? 0 : 1),
13292+ ch: 0
13293+ });
13294+ cm.replaceRange(start_text, {
13295+ line: start_line,
13296+ ch: 0
13297+ }, {
13298+ line: start_line + (start_text ? 0 : 1),
13299+ ch: 0
13300+ });
13301+ });
13302+ cm.setSelection({
13303+ line: start_line + (start_text ? 1 : 0),
13304+ ch: 0
13305+ }, {
13306+ line: end_line + (start_text ? 1 : -1),
13307+ ch: 0
13308+ });
13309+ cm.focus();
13310+ } else {
13311+ // no selection, search for ends of this fenced block
13312+ var search_from = cur_start.line;
13313+ if(fencing_line(cm.getLineHandle(cur_start.line))) { // gets a little tricky if cursor is right on a fenced line
13314+ if(code_type(cm, cur_start.line + 1) === "fenced") {
13315+ block_start = cur_start.line;
13316+ search_from = cur_start.line + 1; // for searching for "end"
13317+ } else {
13318+ block_end = cur_start.line;
13319+ search_from = cur_start.line - 1; // for searching for "start"
13320+ }
13321+ }
13322+ if(block_start === undefined) {
13323+ for(block_start = search_from; block_start >= 0; block_start--) {
13324+ line = cm.getLineHandle(block_start);
13325+ if(fencing_line(line)) {
13326+ break;
13327+ }
13328+ }
13329+ }
13330+ if(block_end === undefined) {
13331+ lineCount = cm.lineCount();
13332+ for(block_end = search_from; block_end < lineCount; block_end++) {
13333+ line = cm.getLineHandle(block_end);
13334+ if(fencing_line(line)) {
13335+ break;
13336+ }
13337+ }
13338+ }
13339+ cm.operation(function() {
13340+ cm.replaceRange("", {
13341+ line: block_start,
13342+ ch: 0
13343+ }, {
13344+ line: block_start + 1,
13345+ ch: 0
13346+ });
13347+ cm.replaceRange("", {
13348+ line: block_end - 1,
13349+ ch: 0
13350+ }, {
13351+ line: block_end,
13352+ ch: 0
13353+ });
13354+ });
13355+ cm.focus();
13356+ }
13357+ } else if(is_code === "indented") {
13358+ if(cur_start.line !== cur_end.line || cur_start.ch !== cur_end.ch) {
13359+ // use selection
13360+ block_start = cur_start.line;
13361+ block_end = cur_end.line;
13362+ if(cur_end.ch === 0) {
13363+ block_end--;
13364+ }
13365+ } else {
13366+ // no selection, search for ends of this indented block
13367+ for(block_start = cur_start.line; block_start >= 0; block_start--) {
13368+ line = cm.getLineHandle(block_start);
13369+ if(line.text.match(/^\s*$/)) {
13370+ // empty or all whitespace - keep going
13371+ continue;
13372+ } else {
13373+ if(code_type(cm, block_start, line) !== "indented") {
13374+ block_start += 1;
13375+ break;
13376+ }
13377+ }
13378+ }
13379+ lineCount = cm.lineCount();
13380+ for(block_end = cur_start.line; block_end < lineCount; block_end++) {
13381+ line = cm.getLineHandle(block_end);
13382+ if(line.text.match(/^\s*$/)) {
13383+ // empty or all whitespace - keep going
13384+ continue;
13385+ } else {
13386+ if(code_type(cm, block_end, line) !== "indented") {
13387+ block_end -= 1;
13388+ break;
13389+ }
13390+ }
13391+ }
13392+ }
13393+ // if we are going to un-indent based on a selected set of lines, and the next line is indented too, we need to
13394+ // insert a blank line so that the next line(s) continue to be indented code
13395+ var next_line = cm.getLineHandle(block_end + 1),
13396+ next_line_last_tok = next_line && cm.getTokenAt({
13397+ line: block_end + 1,
13398+ ch: next_line.text.length - 1
13399+ }),
13400+ next_line_indented = next_line_last_tok && token_state(next_line_last_tok).indentedCode;
13401+ if(next_line_indented) {
13402+ cm.replaceRange("\n", {
13403+ line: block_end + 1,
13404+ ch: 0
13405+ });
13406+ }
13407+
13408+ for(var i = block_start; i <= block_end; i++) {
13409+ cm.indentLine(i, "subtract"); // TODO: this doesn't get tracked in the history, so can't be undone :(
13410+ }
13411+ cm.focus();
13412+ } else {
13413+ // insert code formatting
13414+ var no_sel_and_starting_of_line = (cur_start.line === cur_end.line && cur_start.ch === cur_end.ch && cur_start.ch === 0);
13415+ var sel_multi = cur_start.line !== cur_end.line;
13416+ if(no_sel_and_starting_of_line || sel_multi) {
13417+ insertFencingAtSelection(cm, cur_start, cur_end, fenceCharsToInsert);
13418+ } else {
13419+ _replaceSelection(cm, false, ["`", "`"]);
13420+ }
13421+ }
1314313422}
1314413423
1314513424/**
@@ -13225,7 +13504,10 @@ function drawLink(editor) {
1322513504 var options = editor.options;
1322613505 var url = "http://";
1322713506 if(options.promptURLs) {
13228- url = prompt(options.promptTexts.link) || "http://";
13507+ url = prompt(options.promptTexts.link);
13508+ if(!url) {
13509+ return false;
13510+ }
1322913511 }
1323013512 _replaceSelection(cm, stat.link, options.insertTexts.link, url);
1323113513}
@@ -13239,7 +13521,10 @@ function drawImage(editor) {
1323913521 var options = editor.options;
1324013522 var url = "http://";
1324113523 if(options.promptURLs) {
13242- url = prompt(options.promptTexts.image) || "http://";
13524+ url = prompt(options.promptTexts.image);
13525+ if(!url) {
13526+ return false;
13527+ }
1324313528 }
1324413529 _replaceSelection(cm, stat.image, options.insertTexts.image, url);
1324513530}
@@ -13845,12 +14130,13 @@ var insertTexts = {
1384514130};
1384614131
1384714132var promptTexts = {
13848- link: "URL for link:",
13849- image: "The URL of image:"
14133+ link: "URL for the link:",
14134+ image: "URL of the image:"
1385014135};
1385114136
1385214137var blockStyles = {
1385314138 "bold": "**",
14139+ "code": "```",
1385414140 "italic": "*"
1385514141};
1385614142
@@ -13940,15 +14226,17 @@ function SimpleMDE(options) {
1394014226
1394114227
1394214228 // Set default options for parsing config
13943- options.parsingConfig = options.parsingConfig || {};
14229+ options.parsingConfig = extend({
14230+ highlightFormatting: true // needed for toggleCodeBlock to detect types of code
14231+ }, options.parsingConfig || {});
1394414232
1394514233
1394614234 // Merging the insertTexts, with the given options
1394714235 options.insertTexts = extend({}, insertTexts, options.insertTexts || {});
1394814236
1394914237
1395014238 // Merging the promptTexts, with the given options
13951- options.promptTexts = extend({}, promptTexts, options.promptTexts || {}) ;
14239+ options.promptTexts = promptTexts;
1395214240
1395314241
1395414242 // Merging the blockStyles, with the given options
0 commit comments