Markdown Editor (study)
単純なエディターを作ってみた。
React使用。
by Masaki Moriwaki
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.2/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.2/react-dom.js"></script>
<link rel="stylesheet" href="//cdn.materialdesignicons.com/1.3.41/css/materialdesignicons.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.33/browser.js"></script>
<link rel="stylesheet" href="https://cdn.rawgit.com/twbs/bootstrap/v4-dev/dist/css/bootstrap.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/5.0.1/markdown-it.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.js"></script>
<script src="https://facebook.github.io/react/js/jsfiddle-integration-babel.js"></script>
<div id="editor">
<!-- This element's contents will be replaced with your component. -->
</div>
CSS
.mdi::before {
font-size: 24px;
line-height: 14px;
}
.btn .mdi::before {
position: relative;
top: 4px;
}
.btn-sm .mdi::before {
font-size: 18px;
top: 3px;
}
.mdi-stack {
position: relative;
display: inline-block;
width: 18px;
height: 18px;
line-height: 14px;
vertical-align: middle;
}
.mdi-stack-1x {
position: absolute;
left: 0;
width: 100%;
text-align: center;
}
html, body, #editor {
width: 100%;
height: 100%;
}
.editor {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.editor .btn {
background-color: transparent;
}
.editor .btn:hover,
.editor .btn:active {
border-color: #ccc;
}
.editor > header {
padding: 0.5rem;
}
.editor > header > h2 {
margin: 0;
padding: 0;
}
.editor > header > h2 > input,
.editor > .viewport > .markdown > textarea {
width: 100%;
border: 0;
}
.editor ::-webkit-input-placeholder {
color: #ccc;
}
.editor :-moz-placeholder { /* Firefox 18- */
color: #ccc;
}
.editor ::-moz-placeholder { /* Firefox 19+ */
color: #ccc;
}
.editor :-ms-input-placeholder {
color: #ccc;
}
.editor > header {
border-bottom: 1px solid #aaa;
}
.editor > .viewport {
flex-grow: 1;
display: flex;
flex-direction: row;
}
.editor > .viewport > .markdown {
flex: 1 1 50%;
display: flex;
flex-direction: column;
border-right: 1px solid #aaa;
}
.editor > .viewport > .preview {
flex: 1 1 50%;
display: flex;
flex-direction: column;
}
.editor > .viewport > .markdown > header {
padding: 0.5rem;
}
.editor > .viewport > .markdown > textarea {
flex-grow: 1;
resize: none;
overflow-y: auto;
padding: 0.5rem;
}
.editor > .viewport > .markdown > footer {
padding: 0.5rem;
color: #888;
border-top: 1px solid #aaa;
}
.editor > .viewport > .preview > article {
flex-grow: 1;
overflow-y: auto;
padding: 0.5rem;
}
.editor > .viewport > .preview > footer {
...
JavaScript 1.7
const md = window.markdownit({
html: true,
xhmlOut: true,
breaks: true,
linkify: true
});
class Editor extends React.Component {
constructor(props) {
super(props);
this.preview = _.debounce(this.preview, 1000).bind(this);
this.bold = this.decorate.bind(this, '**', '**', '太字');
this.italic = this.decorate.bind(this, '*', '*', '斜体');
this.underline = this.decorate.bind(this, '<u>', '</u>', '下線');
this.strikethrough = this.decorate.bind(this, '~~', '~~', '取り消し線');
}
preview() {
const html = md.render(this.refs.markdown.value);
this.refs.preview.innerHTML = html;
}
decorate(pre = '', post = '', altenate = '') {
const textarea = this.refs.markdown;
const selectionStart = textarea.selectionStart;
const selectionEnd = textarea.selectionEnd;
const oldText = textarea.value;
const selection = oldText.substring(selectionStart, selectionEnd) || altenate;
const lineBreak = /([\r\n]+)/;
textarea.value = oldText.substring(0, selectionStart) + selection.split(lineBreak).reduce((prev, curr) => (lineBreak.test(curr) ? prev + curr : prev + pre + curr + post), '') + oldText.substring(selectionEnd);
this.preview();
}
render() {
return (
<div className="editor">
<header>
<h2><input type="text" ref="title" placeholder="タイトル" /></h2>
</header>
<div className="viewport">
<div className="markdown">
<header>
<button title="太字" type="button" className="btn btn-sm" onClick={this.bold}>
<i className="mdi mdi-format-bold"></i>
</button>
<button title="斜体" type="button" className="btn btn-sm" onClick={this.italic}>
<i className="mdi mdi-format-italic"></i>
</button>
<button title="下線" type="button" className="btn btn-sm" onClick={this.underline}>
<i className="mdi mdi-format-underline"></i>
</button>
...