http://segmentfault.com/ 怎麼總是莫名其妙地掛掉?網頁會莫名其妙地打不開。
好吧,我且不說這事了。今天我花了一天時間在改造一個Markdown 在線編輯器,終於把它改造得滿符合個人想法了。哈哈,好有成就感。我曾經在網上試用了不少markdown在線編輯器,發現絕大部分都有一個毛病:在輸入框裏敲下Tab鍵,它不是自動插入一個tab製表符,而是焦點自動跳到下一個連接處了。這對常常要寫代碼的我簡直是抓狂。好在我終於找到了一個在線編輯器 http://lab.lepture.com/editor/,它對Tab鍵的處理剛好好處。可是我以爲還不夠完美,因而本身動手改造它。
先說下我作了點什麼修改,看圖:
我加了一幾個按鈕:插入視頻,插入音樂,插入代碼,併爲它們一一分配了快捷鍵。而且還爲Ctrl+S分配了快速提交功能。
其次是粘貼功能,這是我今天改造的重頭戲。我以爲把網頁上的內容粘貼到這個在線編輯器裏,還得手工把它修改爲Markdown代碼,太費事了。因而但願可以自動完成。另外,上傳圖片,原本它是沒有圖片上傳功能的,只能手工輸入圖片地址。費事啊!我也把這個功能集成到粘貼功能裏了。
首先,須要在在線編輯器中綁定onPaste
事件。
我看到那個editor.js中第1580行中有onKeyPress
事件綁定。我先給它加了一個onPaste
事件綁定。javascript
javascripton(d.input, "input", bind(fastPoll, cm)); on(d.input, "keydown", operation(cm, onKeyDown)); on(d.input, "keypress", operation(cm, onKeyPress)); on(d.input, "paste", operation(cm,onPaste)); // 這句是我添加的 on(d.input, "focus", bind(onFocus, cm)); on(d.input, "blur", bind(onBlur, cm));
而後 ,須要寫一個onPaste
函數。我把它寫在onKeyPress函數後面。
我先是想實如今粘貼時自動把HTML代碼轉換成Markdown的功能。因而寫了這麼一個函數。php
javascriptfunction onPaste(e){ if(!e.clipboardData)return true; //IE瀏覽器不支持e.clipboardData對象,無奈 if(e.clipboardData.types=='text/plain')return true; // 若是剪貼板中的內容是純文本內容,直接粘貼。 else if(e.clipboardData.types=='text/plain,text/html'){ // 若是剪貼板中的內容是HTML內容,則須要對它進行一番改造 var html=e.clipboardData.getData('text/html'); html=html.replace(/<html>(\r?\n)+<body>(\r?\n)+<!--StartFragment-->(.*?)<!--EndFragment-->(\r?\n)+<\/body>(\r?\n)+<\/html>/,"$3"); html=toMarkdown(html); // toMarkdown函數 http://segmentfault.com/a/1190000002723901 在這裏已經寫了 var cm=this; _replaceSelection(cm, false, html,''); e.preventDefault(); } }
這裏有一個很詳細的剪貼板js原生對象的介紹:http://wizard.ae.krakow.pl/~jb/localio.html
原本這樣算是大功告成了,可是我又以爲還有點不甘心,由於我但願之後粘貼圖片方便點。
因而我繼續修改這個onPaste
函數,並加了一個圖片上傳功能。html
javascriptfunction onPaste(e){ if(!e.clipboardData)return true; if(e.clipboardData.types=='text/plain')return true; else if(e.clipboardData.types=='text/plain,text/html'){ var html=e.clipboardData.getData('text/html'); html=html.replace(/<html>(\r?\n)+<body>(\r?\n)+<!--StartFragment-->(.*?)<!--EndFragment-->(\r?\n)+<\/body>(\r?\n)+<\/html>/,"$3"); html=toMarkdown(html); var cm=this; _replaceSelection(cm, false, html,''); e.preventDefault(); } else if(e.clipboardData.types=='text/html,Files'){ imgReader(e.clipboardData.items[1]) e.preventDefault(); } else if(e.clipboardData.types=='Files'){ imgReader(e.clipboardData.items[0]) } } function imgReader(item){ if(item.kind=='file'&&item.type=='image/png'){ var file = item.getAsFile(),reader = new FileReader(); reader.onload = function( e ){ var img = new Image(); img.src = e.target.result; document.body.appendChild( img ); // 把圖片放在網頁最下面,以便預覽 $.post('saveremoteimg.php',{'urls':e.target.result},function(data){ _replaceSelection(editor.codemirror,false , '\n'); }) }; reader.readAsDataURL(file); } };
saveremoteimg.php的源碼是:java
php<?php header('Content-Type: text/html; charset=UTF-8'); $attachDir='upload';//上傳文件保存路徑,結尾不要帶/ $dirType=1;//1:按天存入目錄 2:按月存入目錄 3:按擴展名存目錄 建議使用按天存 $maxAttachSize=2097152;//最大上傳大小,默認是2M $upExt="jpg,jpeg,gif,png";//上傳擴展名 ini_set('date.timezone','Asia/Shanghai');//時區 //保存遠程文件 function saveRemoteImg($sUrl){ global $upExt,$maxAttachSize; $reExt='('.str_replace(',','|',$upExt).')'; if(substr($sUrl,0,10)=='data:image'){//base64編碼的圖片,可能出如今firefox粘貼,或者某些網站上,例如google圖片 if(!preg_match('/^data:image\/'.$reExt.'/i',$sUrl,$sExt))return false; $sExt=$sExt[1]; $imgContent=base64_decode(substr($sUrl,strpos($sUrl,'base64,')+7)); } else{//url圖片 if(!preg_match('/\.'.$reExt.'$/i',$sUrl,$sExt))return false; $sExt=$sExt[1]; $imgContent=getUrl($sUrl); } if(strlen($imgContent)>$maxAttachSize)return false;//文件體積超過最大限制 $sLocalFile=getLocalPath($sExt); file_put_contents($sLocalFile,$imgContent); //檢查mime是否爲圖片,須要php.ini中開啓gd2擴展 $fileinfo= @getimagesize($sLocalFile); if(!$fileinfo||!preg_match("/image\/".$reExt."/i",$fileinfo['mime'])){ @unlink($sLocalFile); return false; } return $sLocalFile; } //抓URL數據 function getUrl($sUrl,$jumpNums=0){ $arrUrl = parse_url(trim($sUrl)); if(!$arrUrl)return false; $host=$arrUrl['host']; $port=isset($arrUrl['port'])?$arrUrl['port']:80; $path=$arrUrl['path'].(isset($arrUrl['query'])?"?".$arrUrl['query']:""); $fp = @fsockopen($host,$port,$errno, $errstr, 30); if(!$fp)return false; $output="GET $path HTTP/1.0\r\nHost: $host\r\nReferer: $sUrl\r\nConnection: close\r\n\r\n"; stream_set_timeout($fp, 60); @fputs($fp,$output); $Content=''; while(!feof($fp)) { $buffer = fgets($fp, 4096); $info = stream_get_meta_data($fp); if($info['timed_out'])return false; $Content.=$buffer; } @fclose($fp); global $jumpCount;//重定向 if(preg_match("/^HTTP\/\d.\d (301|302)/is",$Content)&&$jumpNums<5) { if(preg_match("/Location:(.*?)\r\n/is",$Content,$murl))return getUrl($murl[1],$jumpNums+1); } if(!preg_match("/^HTTP\/\d.\d 200/is", $Content))return false; $Content=explode("\r\n\r\n",$Content,2); $Content=$Content[1]; if($Content)return $Content; else return false; } //建立並返回本地文件路徑 function getLocalPath($sExt){ global $dirType,$attachDir; switch($dirType) { case 1: $attachSubDir = 'day_'.date('ymd'); break; case 2: $attachSubDir = 'month_'.date('ym'); break; case 3: $attachSubDir = 'ext_'.$sExt; break; } $newAttachDir = $attachDir.'/'.$attachSubDir; if(!is_dir($newAttachDir)) { @mkdir($newAttachDir, 0777); @fclose(fopen($newAttachDir.'/index.htm', 'w')); } PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000); $newFilename=date("YmdHis").mt_rand(1000,9999).'.'.$sExt; $targetPath = $newAttachDir.'/'.$newFilename; return $targetPath; } $arrUrls=explode('|',$_POST['urls']); $urlCount=count($arrUrls); for($i=0;$i<$urlCount;$i++){ $localUrl=saveRemoteImg($arrUrls[$i]); if($localUrl)$arrUrls[$i]=$localUrl; } echo implode('|',$arrUrls); ?>
想想以爲還有點想改造的。在行內插入代碼是須要在文字左右加兩個點(鍵盤上Tab鍵上方的那個鍵),可是我發如今中文輸入法中,它是自動打出·的,須要切換到英文輸入狀態才能打出想要的那個點。多敲一次鍵盤對我來講都是抓狂。我必須繼續改造它,讓它能像切換粗體或斜體那樣用快捷鍵來實現。
這倒好辦。再寫一個 toggleCode函數,添加在toggleItalic 函數下面:正則表達式
javascriptfunction toggleCode(editor) { var cm = editor.codemirror; var stat = getState(cm); var text; var start = '`'; var end = '`'; var startPoint = cm.getCursor('start'); var endPoint = cm.getCursor('end'); if (stat.code) { text = cm.getLine(startPoint.line); start = text.slice(0, startPoint.ch); end = text.slice(startPoint.ch); start = start.replace(/^(.*)?(`)(\S+.*)?$/, '$1$3'); end = end.replace('`',''); startPoint.ch -= 1; endPoint.ch -= 1; cm.setLine(startPoint.line, start + end); } else { text = cm.getSelection(); cm.replaceSelection(start + text + end); startPoint.ch += 1; endPoint.ch += 1; } cm.setSelection(startPoint, endPoint); cm.focus(); }
而後在shortcuts數組中添加一項'Cmd-Y': toggleCode
,改爲這樣子:segmentfault
javascriptvar shortcuts = { 'Cmd-B': toggleBold, 'Cmd-I': toggleItalic, 'Cmd-Y': toggleCode, // 這項是我加的 'Cmd-K': drawLink, 'Cmd-Alt-I': drawImage, 'Cmd-Q': drawCode, // 這項也是我加入的 'Cmd-\'': toggleBlockquote, 'Cmd-Alt-L': toggleOrderedList, 'Cmd-L': toggleUnOrderedList, 'Cmd-P': togglePreview };
與此同時,getStatus函數須要改爲這樣:數組
javascriptfunction getState(cm, pos) { pos = pos || cm.getCursor('start'); var stat = cm.getTokenAt(pos); if (!stat.type) return {}; var types = stat.type.split(' '); var ret = {}, data, text; for (var i = 0; i < types.length; i++) { data = types[i]; if (data === 'strong') { ret.bold = true; } else if (data === 'variable-2') { text = cm.getLine(pos.line); if (/^\s*\d+\.\s/.test(text)) { ret['ordered-list'] = true; } else { ret['unordered-list'] = true; } } else if (data === 'atom') { ret.quote = true; } else if (data === 'comment'){ // 這句是我加上去的 ret.code = true; // 這句也是我加上去的 } else if (data === 'em') { ret.italic = true; } } return ret; }
我以爲工具欄中沒有按鈕提示很很差。因而改改改~,改爲下面這樣:瀏覽器
javascriptvar toolbar = [ {name: 'bold', action: toggleBold, shortcut:'Toggle Bold(Cmd-B)'}, {name: 'italic', action: toggleItalic, shortcut:'Toggle Italic(Cmd-I)'}, '|', {name: 'quote', action: toggleBlockquote, shortcut: 'toggle Blockquote(Cmd-\')'}, {name: 'unordered-list', action: toggleUnOrderedList, shortcut:'Toggle UnorderList(Cmd-Alt-L)'}, {name: 'ordered-list', action: toggleOrderedList, shortcut:'Toggle OrderList(Cmd-L)'}, '|', {name: 'link', action: drawLink, shortcut:'Insert Link(Cmd-K)'}, {name: 'image', action: drawImage, shortcut: 'Insert Image(Cmd-Alt-I)'}, {name: 'play', action: drawVideo, shortcut: 'Insert Video'}, {name: 'music', action: drawAudio, shortcut: 'Insert Audio'}, {name: 'code', action: drawCode, shortcut: 'Insert Code(Cmd-Q)'}, '|', {name: 'info', action: 'http://lab.lepture.com/editor/markdown'}, {name: 'preview', action: togglePreview, shortcut: 'Toggle Preview'}, {name: 'fullscreen', action: toggleFullScreen, shortcut: 'Toggle FullScreen'} ];
其實我發現原來的程序裏有個小bug,就是用Ctrl+B或者Ctrl+I切換粗體、斜體的時候,第一次按Ctrl+B,會在選中塊去的先後各加兩個星號,而第二次按Ctrl+B的時候,前面的星號去掉了,後面的星號卻沒變化。我仔細看,發現原來的代碼中正則表達式寫錯了。
我修改了toggleBold
和toggleItalic
函數,如今總算正常了。markdown
javascriptfunction toggleBold(editor) { var cm = editor.codemirror; var stat = getState(cm); var text; var start = '**'; var end = '**'; var startPoint = cm.getCursor('start'); var endPoint = cm.getCursor('end'); if (stat.bold) { text = cm.getLine(startPoint.line); start = text.slice(0, startPoint.ch); end = text.slice(startPoint.ch); start = start.replace(/^(.*)?(\*|\_){2}(\S+.*)?$/, '$1$3'); end = end.replace(/(\*|\_){2}/, '');// 這句是我修改過的 startPoint.ch -= 2; endPoint.ch -= 2; cm.setLine(startPoint.line, start + end); } else { text = cm.getSelection(); cm.replaceSelection(start + text + end); startPoint.ch += 2; endPoint.ch += 2; } cm.setSelection(startPoint, endPoint); cm.focus(); } function toggleItalic(editor) { var cm = editor.codemirror; var stat = getState(cm); var text; var start = '*'; var end = '*'; var startPoint = cm.getCursor('start'); var endPoint = cm.getCursor('end'); if (stat.italic) { text = cm.getLine(startPoint.line); start = text.slice(0, startPoint.ch); end = text.slice(startPoint.ch); start = start.replace(/^(.*)?(\*|\_)(\S+.*)?$/, '$1$3'); end = end.replace(/(\*|\_)/, ''); // 這句是我修改過的 startPoint.ch -= 1; endPoint.ch -= 1; cm.setLine(startPoint.line, start + end); } else { text = cm.getSelection(); cm.replaceSelection(start + text + end); startPoint.ch += 1; endPoint.ch += 1; } cm.setSelection(startPoint, endPoint); cm.focus(); }
如今很疲憊,不過總算改得令本身滿意了。掌櫃的站長也改進一下segmentfault.com的在線編輯器吧。app