【应用】Markdown 在线阅读器
前言
一款在线的 Markdown 阅读器,主要用来展示 Markdown 内容。支持 HTML 导出,同时可以方便的添加扩展功能。在这个阅读器的基础又做了一款在线 Github Pages 页面生成器,可以方便的生成不同主题风格的 GitHub Page 页面。
功能
阅读器
- 支持文件拖拽
- 兼容移动端
Prism.js
/Highlight.js
代码高亮- 自动生成目录
- 本地图片显示
- 导出 Html (包含样式)
- 扩展功能
Github Page 生成器
在上面的基础上加上了下面的功能
地址
效果
阅读器
生成器
实现
文件解析
程序使用 marked 将 markdown 格式转为 html 格式,这是一个 js 的库,可以直接在浏览器端使用。下面是一个基本的示例
var htmlContent = marked(mdContent);
$("#content").html(htmlContent);
同时 marked 提供了一些接口,让我们可以方便的定制自己的功能。具体的可以参考它的 说明文件 。在下面我们会介绍我们是如何利用这些接口来实现扩展功能。
文件上传
自定义上传按钮样式
原始的上传按钮太丑了,所以我们需要自定义自己的样式。这里使用的方式是使用在 input
上面覆盖一个 button
,用 button
来显示样式。同时我们将 button
的 pointer-events
设为 none
,就可以阻止 button
的事件响应(具体可以参考这里)。下面是具体的实现代码:
html:
<div class="upload-area" id="upload-area">
<input type="file" id="select-file" class="select-file">
<button class="select-file-style" id="drop">选择或者拖拽 Markdown 文件到此</button>
</div>
css
.upload-area {
width: auto;
height: 200px;
margin: 0 2.6em 0 0.4em;
padding: 0;
position: relative;
cursor: pointer;
transition: height 0.5s;
}
.upload-area .select-file {
border-width: 0px;
width: 100%;
height: 200px;
margin: 0;
cursor: pointer;
}
.upload-area .select-file-style {
background: #F5F7FA;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 200px;
border: 0px;
pointer-events: none;
color: #AAB2BD;
font-size: 2em;
line-height: 2em;
font-family: "Microsoft YaHei", "Tahoma", arial;
}
下面是效果图
读取文件内容
因为程序完全是运行在浏览器端,所以我们使用 html5 的 FileReader
来读取本地文件。FileReader
提供 4 种读取文件的方式
readAsBinaryString(Blob|File)
readAsText(Blob|File, opt_encoding)
readAsDataURL(Blob|File)
readAsArrayBuffer(Blob|File)
其中 readAsText
用来读取文本文件,readAsDataUrl
可以用来读取图片。具体的介绍可以参考 这里 。FileReader
一般结合文件选择事件或者拖拽事件使用,因为通过这两个事件可以获得源文件。另外 FileReader
是异步读取的,通过 onload
事件可以监听文件是否读取完毕。下面是一个示例, 通过点击 <input type= "file">
选择文件,然后读取文件内容。
document.getElementById("file-select").addEventListener("change", function(e) {
e.stopPropagation();
e.preventDefault();
var reader = new FileReader();
reader.readAsText(this.files[0]);
reader.onload = function (e) {
var content = e.target.result;
//......
};
}, false);
拖拽文件
为了方便用户操作,我们提供了点击和拖拽两种方式来上传文件。现在的主流浏览器都支持文件拖拽功能,下面是拖拽过程中触发的事件
事件 | 描述 |
---|---|
dragstart | 用户开始拖动对象时触发。 |
dragenter | 鼠标初次移到目标元素并且正在进行拖动时触发。这个事件的监听器应该之指出这个位置是否允许放置元素。如果没有监听器或者监听器不执行任何操作,默认情况下不允许放置。 |
dragover | 拖动时鼠标移到某个元素上的时候触发。 |
dragleave | 拖动时鼠标离开某个元素的时候触发。 |
drag | 对象被拖拽时每次鼠标移动都会触发。 |
drop | 拖动操作结束,放置元素时触发。 |
dragend | 拖动对象时用户释放鼠标按键的时候触发。 |
另外在拖拽过程中是不触发鼠标事件的。文件读取完后文件信息会保存在 DataTransfer
对象中。详细的介绍可以参考 这里 。下面是添加事件的示例
fileSelect.addEventListener("dragenter", dragMdEnter, false);
fileSelect.addEventListener("dragleave", dragMdLeave, false);
fileSelect.addEventListener('drop', dropMdFile, false);
读取拖拽的文件
function dropMdFile(e) {
// 取消浏览器默认行为
e.stopPropagation();
e.preventDefault();
var reader = new FileReader();
reader.readAsText(e.dataTransfer.files[0]);
reader.onload = function (e) {
var content = e.target.result;
//......
};
}
本地图片显示
因为没有服务器,所以为了显示本地图片,使用了替换图片 src
的方式。首先读取本地文件,然后将 <img>
的 src
路径替换为图片内容 。如下所示:
<img src="path">
// 替换为
<img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgI...">
下面是具体的代码实现:
// 读取选择或者拖拽的文件(多个文件)
function processImages(imgFiles) {
var index = 0;
for (i = 0; i < imgFiles.length; i++) {
var file = imgFiles[i];
var reader = new FileReader();
reader.readAsDataURL(file);
(function (reader, file) {
reader.onload = function (e) {
cacheImages[file.name] = e.target.result;
index++;
if (index == length) {
replaceImage();
}
}
})(reader, file);
}
}
// 将路径替换为图片内容
function replaceImage() {
var images = $("img");
var i;
for (i = 0; i < images.length; i++) {
var imgSrc = images[i].src;
var imgName = getImgName(imgSrc);
if (cacheImages.hasOwnProperty(imgName)) {
images[i].src = cacheImages[imgName];
}
}
}
如果图片过大,我们可以将图片压缩一下,具体方法就是创建一个 canvas
元素,将图片绘制到 canvas
上,然后将 canvas
转为图片。这种方式对 jpg
文件压缩效果较好,对 png
文件压缩效果不太好。下面是代码实现:
function compressImage(img, format) {
var max_width = 862;
var canvas = document.createElement('canvas');
var width = img.width;
var height = img.height;
if (format == null || format == "") {
format = "image/png";
}
if (width > max_width) {
height = Math.round(height *= max_width / width);
width = max_width;
}
// resize the canvas and draw the image data into it
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext("2d");
ctx.drawImage(img, 0, 0, width, height);
return canvas.toDataURL(format);
}
循环中使用异步回调函数
为了方便使用,我们可以同时上传多个图片,我们使用 for
循环来读取多个文件,但是有个问题是文件的读取是异步的,也就是说在 for
循环执行完之后,图片可能仍在读取中,当图片读取完后,再调用 onload
回调函数进行处理。简单一点就是说如何在 for
循环中正确使用延迟调用的回调函数。看下面的例子:
function print(value, callback) {
console.log("value in print", value);
setTimeout(callback, 1000);
}
for(var i = 0; i < 4; i++) {
var value = i;
print(value, function() {
console.log("value in callback", value);
});
}
上面打的代码和我们读取图片文件的逻辑类似,callback
函数会在调用 print
函数1秒后执行,下面是输出结果
value in print 0
value in print 1
value in print 2
value in print 3
value in callback 3
value in callback 3
value in callback 3
value in callback 3
最后在 callback
中 value
值都是3,这是因为在 js 中没有块级作用域,只有函数作用域,也就是说下面的两段代码是等同的:
for(var i = 0; i < 4; i++) {
var value = i;
// do someting
}
// 等同于
var value;
for(var i = 0; i < 4; i++) {
value = i;
// do someting
}
因此,为了解决这个问题,我们只需要为循环中的回调函数添加一个单独的作用域即可,我们使用闭包来实现:
for(var i = 0; i < 4; i++) {
var value = i;
(function(value) {
print(value, function() {
console.log("value in callback", value);
});
}(value));
}
代码高亮
我们使用两款代码高亮插件 -- highlight.js 和 prism.js,根据喜好可以自由切换。这两款插件对代码块的 html 格式有不同的要求,我们重写了 marked
中解析代码块的方法,根据高亮方式来生成不同的 html 代码:
renderer.code = function (code, lang) {
if (Setting.highlight == Constants.highlight) {
return "<pre><code class='" + lang + "'>" + code + "</code></pre>";
}
return "<pre><code class='language-" + lang + "'>" + code + "</code></pre>";
};
然后调用 highlight.js 和 prism.js 的代码高亮方法即可
if (Setting.highlight == Constants.highlight) {
$('pre code').each(function (i, block) {
hljs.highlightBlock(block);
});
} else {
// 添加行号支持
$("pre").addClass("line-numbers");
Prism.highlightAll();
}
目录
为了生成文件的目录,我们需要首先获得目录信息,因此我们重写 marked
的 heading
方法, 将目录信息保存起来,同时为每个标题添加链接图标(仿照 github),下面是代码:
renderer.heading = function (text, level) {
var slug = text.toLowerCase().replace(/[\s]+/g, '-');
if (tocStr.indexOf(slug) != -1) {
slug += "-" + tocDumpIndex;
tocDumpIndex++;
}
tocStr += slug;
toc.push({
level: level,
slug: slug,
title: text
});
return "<h" + level + " id=\"" + slug + "\"><a href=\"#" + slug + "\" class=\"anchor\">" + '' +
'<svg aria-hidden="true" class="octicon octicon-link" height="16" version="1.1" viewBox="0 0 16 16" width="16"><path fill-rule="evenodd" d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"></path></svg>' +
'' + "</a>" + text + "</h" + level + ">";
};
同时需要加入下面的 css,以是标题的链接图片正常显示:
h1:hover .anchor, h2:hover .anchor, h3:hover .anchor, h4:hover .anchor, h5:hover .anchor, h6:hover .anchor {
text-decoration: none
}
h1:hover .anchor .octicon-link, h2:hover .anchor .octicon-link, h3:hover .anchor .octicon-link, h4:hover .anchor .octicon-link, h5:hover .anchor .octicon-link, h6:hover .anchor .octicon-link {
visibility: visible
}
.octicon {
display: inline-block;
vertical-align: text-top;
fill: currentColor;
}
.anchor {
float: left;
padding-right: 4px;
margin-left: -20px;
line-height: 1;
}
为了生成目录,我们只需按照保存的目录信息,生成 <ul>
和 <li>
标签即可,具体的可以参考源码中的实现。
配置页面锚链接
目录使用的是页内锚链接的方式进行跳转,如下面所示:
<a href="#h1">跳转到 H1</a>
...
<h1 id="h1">我是 H1</h1>
...
默认情况下,页内锚链接跳转之后,目标标签(上面代码中的 <h1>
)会移动到页面的最顶部,但是在我们的程序中有一个固定的 header,如果跳转到最顶部,目标标签会被 header 遮挡住,所以我们希望目标标签移动到距离页面顶部 header-height
的地方。为了实现我们的需要,只要加入下面的 css 代码即可。
:target:before {
content:"";
display:block;
height:50px; /* fixed header height*/
margin:-50px 0 0; /* negative fixed header height */
}
Todo 列表
Todo 列表实际上就是 checkbox 的列表,完成的工作用选中的 checkbox 表示,未完成的工作用喂选中的列表表示,如下图所示:
一般来说,会将下面形式的 markdown 代码解析为 todo 列表
- [x] 完成
- [ ] 未完成
- [ ] 未完成
为了实现这个功能,我们重写 marked
中解析列表的方法,加入对 todo 列表的支持。
renderer.listitem = function (text) {
if (/^\s*\[[x ]\]\s*/.test(text)) {
text = text
.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled> ')
.replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled checked> ');
return '<li style="list-style: none">' + text + '</li>';
} else {
return '<li>' + text + '</li>';
}
};
同时加入下面的样式:
.task-list-item-checkbox {
margin: 0 0.2em 0.25em -2.3em;
vertical-align: middle;
}
[type="checkbox"], [type="radio"] {
box-sizing: border-box;
padding: 0;
}
缓存
现在的浏览器都已经支持 localStorage
,可以方便的存储数据。localStorage
就是一个对象。我们存储数据就是直接给它添加一个属性,可以通过 localStoage["a"]=1
或者 localStorage.a = 1
的方式来存储数据,但是看起来总觉的不太优雅,因为一般使用下面的方式来操作 localStorage
:
localStorage.setItem(key, vlaue);
localStorage.getItem(key);
localStorage.removeItem(key);
另外 localStorage
也有一些局限,使用时需要注意:
- 存储空间有限制,一般是
5M
左右,和浏览器有关 - 用户清除浏览器缓存之后有可能丢失本地缓存的数据
- 不能直接存对象,要先使用
JSON.stringfy
方法将对象进行序列化处理之后再保存。使用时需要使用JSON.parse
方法将字符串转为对象。
导出文件
通过使用 FileSaver.js,我们可以方便的在浏览器端生成文件,并提供给用户下载。使用方法也很简单:
var blob = new Blob([htmlContent], {type: "text/html;charset=utf-8"});
saveAs(blob, name);
扩展
我们提供了一些扩展功能,用来更好的展示 markdown 内容。在现在的程序中我们可以很方便的添加扩展功能,下面会具体介绍。
自定义扩展
为了添加扩展,我们首先需要确定哪些内容需要作为扩展处理。因为在将 markdown 文件转为 html 的过程中,一般是不处理代码块中的内容的,所以我们使用代码块来存放扩展内容,通过代码块的语言来确定是哪种扩展。以添加序列图扩展为例:
确定时序图的代码标记
修改
marked
中对于代码块的解析函数,添加对于时序图标记的支持
var renderer = new marked.Renderer();
var originalCodeFun = function (code, lang) {
if (Setting.highlight == Constants.highlight) {
return "<pre><code class='" + lang + "'>" + code + "</code></pre>";
}
return "<pre><code class='language-" + lang + "'>" + code + "</code></pre>";
};
renderer.code = function (code, language) {
if (language == "seq") {
return "<div class='diagram' id='diagram'>" + code + "</div>"
} else {
return originalCodeFun.call(this, code, language);
}
};
marked.setOptions({
renderer: renderer
});
- 引入
js-sequence-diagrams
相关文件
<link href="{{ bower directory }}/js-sequence-diagrams/dist/sequence-diagram-min.css" rel="stylesheet" />
<script src="{{ bower directory }}/bower-webfontloader/webfont.js" />
<script src="{{ bower directory }}/snap.svg/dist/snap.svg-min.js" />
<script src="{{ bower directory }}/underscore/underscore-min.js" />
<script src="{{ bower directory }}/js-sequence-diagrams/dist/sequence-diagram-min.js" />
- 渲染 Markdown 文件时,调用相关函数
$(".diagram").sequenceDiagram({theme: 'simple'});
添加扩展会影响文件的渲染速度,如果不需要某个扩展可以手动关闭。
Mathjax
使用Mathjax 对数学公式进行支持。关于Mathjax 语法,请参考这里。下面是添加扩展的流程:
- 引入文件并配置
<script type="text/x-mathjax-config">
MathJax.Hub.Config({tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']]},
TeX: {
equationNumbers: {
autoNumber: ["AMS"],
useLabelIds: true
}
},
"HTML-CSS": {
linebreaks: {
automatic: true
}
},
SVG: {
linebreaks: {
automatic: true
}
}
});
</script>
<script type="text/javascript" src="http://cdn.bootcss.com/mathjax/2.7.0/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
- 将 markdown 文件转为 html 之后,调用 Mathjax 中的方法将对应标记转为数学公式。
// content 是需要处理的 html 标签的 id
MathJax.Hub.Queue(["Typeset", MathJax.Hub, "content"]);
Emoji
使用 emojify.js 来提供对 Emoji 标签的支持。Emoji表情参见 EMOJI CHEAT SHEET。下面是添加扩展的流程
- 引用文件并配置
<script src="http://cdn.bootcss.com/emojify.js/1.1.0/js/emojify.min.js"></script>
<script type="text/javascript">
emojify.setConfig({
emojify_tag_type: 'div', // Only run emojify.js on this element
only_crawl_id: null, // Use to restrict where emojify.js applies
img_dir: 'http://cdn.bootcss.com/emojify.js/1.0/images/basic', // Directory for emoji images
ignored_tags: { // Ignore the following tags
'SCRIPT': 1,
'TEXTAREA': 1,
'A': 1,
'PRE': 1,
'CODE': 1
}
});
</script>
- 将 markdown 文件转为 html 之后,调用 emojify 中的方法将对应标记转换 emoji 表情。
emojify.run(document.getElementById('content'))
图表 (ECharts)
使用 ECharts 来提供对图表的支持。ECharts 的语法可以参考 官网的示例。下面是使用方法:
确定 ECharts 在 markdown 中的语法标签
在 code 方法解析中添加对 echarts 的支持
renderer.code = function (code, language) {
switch (language) {
case "echarts":
if (Setting.echarts) {
return loadEcharts(code);
}
return originalCodeFun.call(this, code, language);
}
};
function loadEcharts(text) {
var width = "100%";
var height = "400px";
try {
var options = eval("(" + text + ")");
if (options.hasOwnProperty("width")) {
width = options["width"];
}
if (options.hasOwnProperty("height")) {
height = options["height"];
}
echartIndex++;
echartData.push({
id: echartIndex,
option: options,
previousOption: text
});
return '<div id="echarts-' + echartIndex + '" style="width: ' + width + ';height:' + height + ';"></div>'
} catch (e) {
console.log(e);
return "";
}
}
- 将 markdown 文件转为 html 之后,调用 echarts 中的方法,将对应的 div 转为图表:
var chart;
echartData.forEach(function (data) {
if (data.option.theme) {
chart = echarts.init(document.getElementById('echarts-' + data.id), data.option.theme);
} else {
chart = echarts.init(document.getElementById('echarts-' + data.id));
}
chart.setOption(data.option);
});
评论
在生成Github Page页面时,我们可以选择添加 多说 或者 Disqus 评论,其中多说就是在导出的页面中加入下面的代码
<div class="ds-thread" data-thread-key="" data-title="" data-url=""></div>
<script type="text/javascript">
var duoshuoQuery = {
short_name: ""
};
(function() {
var ds = document.createElement("script");
ds.type = "text/javascript";
ds.async = true;
ds.src = (document.location.protocol == "https:" ? "https:" : "http:") + "//static.duoshuo.com/embed.js";
ds.charset = "UTF-8";
(document.getElementsByTagName("head")[0] || document.getElementsByTagName("body")[0]).appendChild(ds);
})();
</script>
其中 data-thread-key
, data-title
, data-url
和 short_name
是需要我们自定义的东西。而Disqus 需要在导出时插入下面的代码:
<div id="disqus_thread"></div>
<script type="text/javascript">
var disqus_shortname = '';
var prefix = document.location.protocol == "https:" ? "https:" : "http:"
var disqus_config = function() {
this.page.url = "";
this.page.identifier = ""
};
(function() {
var d = document,
s = d.createElement('script');
s.src = prefix + '//' + disqus_shortname + '.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
其中 disqus_shortname
, page.url
和 page.indertifier
是需要我们自定义的东西。这里需要注意的是 page.url
要使用绝对路径。
具体的插入逻辑可参考源码的实现,这里不再赘述。
【应用】Markdown 在线阅读器的更多相关文章
- Atitit rss没落以及替代品在线阅读器
Atitit rss没落以及替代品在线阅读器 1.1. 对RSS的疯狂追逐,在2005年达到了一个高峰.1 1.2. Rss的问题,支持支rss,不支持url1 1.3. ,博客受到社交网络的冲击.s ...
- 记录思想分享知识编辑器 Markdown 编辑阅读器
web使用:实现网页客户端实时自动解析Markdown为HTML内容小小的展示:Cmd Markdown 编辑阅读器使用必要性:怎样引导新手使用 Markdown? - 写作
- web版pdf在线阅读器
近期论坛里有人提到了,在线pdf阅读器怎么做.我百度了一下,发现非常多人都非常懒.程序猿都非常懒吗? 答案是否定的. 为什么都不愿意去搜索一下呢.网上非常多答案的.我这里就列举一例.大家共勉. 看代码 ...
- Cmd Markdown 编辑阅读器
我们理解您需要更便捷更高效的工具记录思想,整理笔记.知识,并将其中承载的价值传播给他人,Cmd Markdown 是我们给出的答案 -- 我们为记录思想和分享知识提供更专业的工具. 您可以使用 Cmd ...
- UWP_小说在线阅读器:功能要求与技术要求
学了WP开发也有一年了,也没做过什么软件的.17年进发UWP,锻炼自己一下.做一个开源的小说阅读器吧. 既然开发一个软件.所以要设计一下吧. 功能要求: 可能要用到的技术,这个吗,这就是遇到问题在解决 ...
- Silverlight类百度文库在线文档阅读器
百度文库阅读器是基于Flash的,用Silverlight其实也可以做. 我实现的在线阅读器可以应用于内网文档发布,在线阅览审批等.没有过多的堆积功能,专注于核心功能.主要有以下特性: 1. 基于XP ...
- Silverlight类百度文库在线文档阅读器(转)
百度文库阅读器是基于Flash的,用Silverlight其实也可以做. 我实现的在线阅读器可以应用于内网文档发布,在线阅览审批等.没有过多的堆积功能,专注于核心功能.主要有以下特性: 1. 基于XP ...
- 【02】markdown在线编辑器
[01]在线编辑器 https://www.zybuluo.com/mdeditor 在线 Markdown 编辑阅读器 pen - 是一个Markdown编辑器工具.demo 你可以试试这个在线的m ...
- 为什么说Thunderbird是最好的桌面RSS阅读器
也许现在再讨论RSS阅读器似乎已经过时了,毕竟随着社交网络服务的发展,通过一个带有大众评分能力的社交网络(比如reddit),相比RSS的固定订阅而言,也许你能更快地在你所关心的话题上更快地获得新的资 ...
随机推荐
- [l转]VLM_on_Linux
Instructions for running the Symbolics VLM virtual machine on Linux. VLM On Linux From LispMachinery ...
- ipad安装自制ipa
自己用XCode写了个小程序,想打包成ipa安装在真机上,网上查了查: 1.将工程的编译版本设置为release(在edit scheme里): 2.build for Archiving(Produ ...
- Custom.pm
1) 只有一种事情比你培训员工.培养员工然后他们离开要更糟糕,那就是你不培训他们.不培养他们,但他们仍然留下来. 2) PM的含义: Product Manager, Project Manager, ...
- C# 编写服务 Windows service
1.编写服务教程 http://jingyan.baidu.com/article/ea24bc395e16f8da62b331e7.html 这里不多说了. 给大家一个连接,上面有详细的教程,下面说 ...
- WPF 利用子线程弹出子窗体的研究
一般来说子线程都是用来处理数据的,主窗体用来实现展现,但是有些时候我们希望子窗体实现等待效果,遮挡主窗体并使主窗体逻辑正常进行,这个业务需求虽然不多,但是正好我们用到了,于是我打算把研究成果写在这了. ...
- JavaEE Tutorials (30) - Duke综合案例研究示例
30.1Duke综合应用的设计和架构456 30.1.1events工程458 30.1.2entities工程459 30.1.3dukes—payment工程461 30.1.4dukes—res ...
- bzoj1641 [Usaco2007 Nov]Cow Hurdles 奶牛跨栏
Description Farmer John 想让她的奶牛准备郡级跳跃比赛,贝茜和她的伙伴们正在练习跨栏.她们很累,所以她们想消耗最少的能量来跨栏. 显然,对于一头奶牛跳过几个矮栏是很容易的,但是高 ...
- hdu 3874 Necklace(bit树+事先对查询区间右端点排序)
Mery has a beautiful necklace. The necklace is made up of N magic balls. Each ball has a beautiful v ...
- Poj 2187 Beauty Contest_旋转凸包卡壳
题意:给你n个坐标,求最远的两点距离 思路:用凸包算法求处,各个定点,再用旋转凸包卡壳 #include <iostream> #include <cstdio> #inclu ...
- cmd用到的基本操作
dir #显示当前目录中的文件和子目录 dir /a #显示当前目录中的文件和子目录,包括隐藏文件和系统文件 a = all dir c: /a:d #显示 C 盘当前目录中的目录 d = direc ...