MarkWord - 可发布博客的 Markdown编辑器 代码开源
因为前一段时间看到 NetAnalyzer 在Windows10系统下UI表现惨不忍睹,所以利用一段时间为了学习一下WPF相关的内容,于是停停写写,用了WPF相关的技术,两个星期做了一个Markdown编辑器,并且集成了:编辑时与网页同步,博客发布,PDF导出等功能。也主要是不忿某款外国软件收费,故有此作。
代码下载地址
https://github.com/Twzy/MarkWord
展示与说明
代码同步编辑
博客发布
代码说明
博客发布
MarkWord支持博客园和CSDN博客发布,并且可以进行图片同步(无论是本地图片还是网上的图片,都可以同步到博客服务器)。 该功能使用了MetaWeblog技术。使用方法如下,其中图片上传为newMediaObject 接口
1 /// <summary> 2 /// 文档上传,包括新增与更新 3 /// </summary> 4 public static string UploadBlogs(string apiUrl, string BlogId, string userId, string password, string 5 BlogsModel, string postId, string title, string Markdown, bool publish) 6 { 7 8 int procIndex = 1; 9 10 SendMsg(5, procIndex, "准备数据中……"); 11 //转换为html 12 string Blogs = string.Format("<!-- edit by MarkWord 墨云软件 -->\r\n{0}", 13 CommonMark.CommonMarkConverter.Convert(Markdown)); 14 metaTools.Url = apiUrl; 15 16 17 Post blogsPost = new Post(); 18 19 //分类 20 List<string> tmpCategories = new List<string>(); 21 tmpCategories.Add("");//添加空分类,是因为部分博客(如csdn)字段这部分为必填字段不添加会产生异常 22 blogsPost.categories = tmpCategories.ToArray(); 23 24 //添加时间 25 blogsPost.dateCreated = DateTime.Now.ToLocalTime(); 26 27 //添加标题 28 blogsPost.title = title; 29 30 31 //指定文章编号 32 blogsPost.postid = postId; 33 34 //内容 35 blogsPost.description = BlogsModel.Contains("{0}") ?//必须使用{0}占位符 36 string.Format(BlogsModel, Blogs) : //根据模板生成数据 主要是为了制定Markdown模板 37 BlogsModel + Blogs; //通过前缀方式添加 38 39 //开始查找图片并更新到服务器 40 HtmlDocument htmlDoc = new HtmlDocument(); 41 WebClient webClient = new WebClient(); 42 htmlDoc.LoadHtml(blogsPost.description); 43 var ImgList = htmlDoc.DocumentNode.Descendants("img"); 44 45 int procCount = 3 + ImgList.Count(); 46 47 SendMsg(procCount, procIndex++, string.Format("数据分析完成,总共需要上传{0}张图片", ImgList.Count())); 48 int imgErr = 0;//图片上传错误数量 49 foreach (var i in ImgList) 50 { 51 SendMsg(procCount, procIndex++, "正在上传图片数据……"); 52 //获取图片文件字符串 53 string ImgUrl = i.GetAttributeValue("src", ""); 54 if (string.IsNullOrEmpty(ImgUrl)) 55 { 56 imgErr++; 57 continue; 58 } 59 try 60 { 61 var imgeData = webClient.DownloadData(ImgUrl);//下载文件 62 63 FileData fd = default(FileData); 64 fd.bits = imgeData;//图片数据 65 fd.name = Path.GetExtension(ImgUrl);//文件名 66 fd.type = string.Format("image/{0}", fd.name.Substring(1)); 67 68 UrlData obj = metaTools.newMediaObject(BlogId, userId, password, fd); 69 blogsPost.description = blogsPost.description.Replace(ImgUrl, obj.url); 70 } 71 catch 72 { 73 imgErr++; 74 continue; 75 } 76 } 77 try 78 { 79 if (string.IsNullOrWhiteSpace(postId)) 80 { 81 SendMsg(procCount, procIndex++, "开始发布文章……"); 82 postId = metaTools.newPost(BlogId, userId, password, blogsPost, publish); 83 } 84 else 85 { 86 SendMsg(procCount, procIndex++, "正在更新文章……"); 87 metaTools.editPost(postId, userId, password, blogsPost, publish); 88 } 89 } 90 catch (Exception ex) 91 { 92 Common.ShowMessage("博客发送失败"); 93 return postId; 94 } 95 96 if (imgErr == 0) 97 { 98 Common.ShowMessage("博客发送成功"); 99 } 100 else 101 { 102 Common.ShowMessage(string.Format("博客发送成功了,但是有{0}张图片发送失败", imgErr)); 103 } 104 SendMsg(procCount, procCount, "完成"); 105 return postId; 106 107 }
具体API实现方法见代码中的BlogsAPI项目
PDF导出
PDF导出功能,使用了HTML转PDF方法 相关DLL已经包含在项目当中了
1 //html to Pdf 2 public static void HtmlToPdf(string filePath, string html, bool isOrientation = false) 3 { 4 if (string.IsNullOrEmpty(html)) 5 html = "Null"; 6 // 创建全局信息 7 GlobalConfig gc = new GlobalConfig(); 8 gc.SetMargins(new Margins(50, 50, 60, 60)) 9 .SetDocumentTitle("MarkWord") 10 .SetPaperSize(PaperKind.A4) 11 .SetPaperOrientation(isOrientation) 12 .SetOutlineGeneration(true); 13 14 15 //页面信息 16 ObjectConfig oc = new ObjectConfig(); 17 oc.SetCreateExternalLinks(false) 18 .SetFallbackEncoding(Encoding.UTF8) 19 .SetLoadImages(true) 20 .SetScreenMediaType(true) 21 .SetPrintBackground(true); 22 //.SetZoomFactor(1.5); 23 24 var pechkin = new SimplePechkin(gc); 25 pechkin.Finished += Pechkin_Finished; 26 pechkin.Error += Pechkin_Error; 27 pechkin.ProgressChanged += Pechkin_ProgressChanged; 28 var buf = pechkin.Convert(oc, html); 29 30 if (buf == null) 31 { 32 Common.ShowMessage("导出异常"); 33 return; 34 } 35 36 try 37 { 38 string fn = filePath; //Path.GetTempFileName() + ".pdf"; 39 FileStream fs = new FileStream(fn, FileMode.Create); 40 fs.Write(buf, 0, buf.Length); 41 fs.Close(); 42 43 //Process myProcess = new Process(); 44 //myProcess.StartInfo.FileName = fn; 45 //myProcess.Start(); 46 } 47 catch { } 48 }
CommonMark使用
最后就Markdown的转换,在这里我使用了CommonMark,使用方法比较简单
CommonMark.CommonMarkConverter.Convert("### test")
编辑与html页面同步原理
在改工具中比较有意思的就是编辑器与Webbrower的页面同步功能,包括页面"无刷新"同步呈现,已经页面同步滚动,在这里使用的是编辑器触发 textEditor_TextChanged 事件和 ScrollViewer 触发的scrViewer_ScrollChanged 分别通过webbrowser 的 InvokeScript 动态调用Js实现的,我们先来看看两个js的内容
同步呈现
function updatePageContent(msg){ document.body.innerHTML= msg; }
非常简单,只是将转换出来的html直接通过document.body.innerHTML 赋给当前页面既可以了。
同步滚动
function scrollToPageContent(value){ window.scrollTo(0, value * (document.body.scrollHeight - document.body.clientHeight)); }
这部分,是需要通过WPF页面转过来一个对应的页面位移高度与窗口显示高度的一个页面比例,然后在webbrowser中根据该比例计算页面需要偏移量来实现同步移动
而对应的WPF端的代码为
1 /// <summary> 2 /// 同步呈现 3 /// </summary> 4 /// <param name="value"></param> 5 public void LoadBody(string MarkValue) 6 { 7 8 if (winWebDoc.Document == null) 9 return; 10 winWebDoc.Document.InvokeScript("updatePageContent", new object[] { CommonMark.CommonMarkConverter.Convert(MarkValue) }); 11 } 12 13 /// <summary> 14 /// 文本更变 15 /// </summary> 16 /// <param name="sender"></param> 17 /// <param name="e"></param> 18 private void textEditor_TextChanged(object sender, EventArgs e) 19 { 20 if (!isLoadFlag) 21 { 22 if (this.textEditor.Text != "" && scrViewer != null) 23 if (scrViewer.ScrollableHeight == scrViewer.VerticalOffset) 24 scrViewer.ScrollToBottom(); 25 26 BLL.FileManager.isChangeFlag = true; 27 } 28 //加载文档 29 if (MarkDoc == null) 30 return; 31 if (Config.Common.WorkType == WorkType.Both) 32 { 33 MarkDoc.LoadBody(this.textEditor.Text); 34 } 35 } 36 ////////////////////////////////////////////////////////////////////////////////// 37 /// <summary> 38 /// 同步滚动 39 /// </summary> 40 /// <param name="value"></param> 41 public void ScrollAuto(double value) 42 { 43 if (winWebDoc.Document == null) 44 return; 45 winWebDoc.Document.InvokeScript("scrollToPageContent", new object[] { value.ToString(System.Globalization.CultureInfo.InvariantCulture) }); 46 47 } 48 //计算比例 49 public double ScrollViewerPositionPercentage 50 { 51 get 52 { 53 double num = this.scrViewer.ExtentHeight - this.scrViewer.ViewportHeight; 54 double result; 55 if (num != 0.0) 56 { 57 result = this.scrViewer.VerticalOffset / num; 58 } 59 else 60 { 61 result = 0.0; 62 } 63 return result; 64 } 65 } 66 67 //触发同步 68 private void scrViewer_ScrollChanged(object sender, ScrollChangedEventArgs e) 69 { 70 if (MarkDoc == null) return; 71 if (Config.Common.WorkType == WorkType.Both) 72 { 73 MarkDoc.ScrollAuto(this.ScrollViewerPositionPercentage); 74 } 75 }
至此,Markword 中设计到的内容点已经基本覆盖到了,如有疑问欢迎交流!!!
最后来一发小广告
NetAnalyzer2016网络协议分析软件源码开放购买,可以分析80多种协议,支持http数据还原(包含chunked和gzip数据) ,欢迎大家可以支持一下!!
墨云NetAnalyzer官网
代码购买链接
如有疑问欢迎QQ联系:470200051
祝大家周末愉快