Skip to content

Instantly share code, notes, and snippets.

@kshep92
Last active December 15, 2016 04:11
Show Gist options
  • Save kshep92/ff0b158700c4df3603e89db47b24acc5 to your computer and use it in GitHub Desktop.
Save kshep92/ff0b158700c4df3603e89db47b24acc5 to your computer and use it in GitHub Desktop.
How to create a Ghost-like editor with image placeholders that facilitate file upload. Sweeten to taste. From the THLabs/ghostdown repo.
'use strict';
var textarea = document.querySelector('#ghostdown-editor'),
preview = document.querySelector('#ghostdown-rendered'),
converter = new showdown.Converter(),
editor = CodeMirror.fromTextArea(textarea, {
mode: 'markdown',
tabMode: 'indent',
lineWrapping: true
});
function updateImagePlaceholders() {
// Get all the empty images. I would probably just do a document.querySelectorAll('img[src=""]')
var imgPlaceholders = (Array.prototype.slice.call(document.querySelectorAll('#ghostdown-rendered p'))).filter(function (p) {
return (/^(?:\{<(.*?)>\})?!(?:\[([^\n\]]*)\])(?:\(([^\n\]]*)\))?$/gim).test(p.innerText);
});
Dropzone.autoDiscover = false;
imgPlaceholders.forEach(
function (element, index) {
element.setAttribute("class", "dropzone");
var dropzone = new Dropzone(element, {
url: "/content/upload",
success: function (file, response) {
var response = JSON.parse(response),
holderP = file.previewElement.closest("p"),
preList = Array.prototype.slice.call(document.querySelectorAll('.CodeMirror-code pre')),
// Not sure what this is testing. Will have to experiment.
imgHolderMarkdown = preList.filter(function (element) {
return (/^(?:\{<(.*?)>\})?!(?:\[([^\n\]]*)\])(?:\(([^\n\]]*)\))?$/gim).test(element.textContent) && (element.querySelectorAll('span').length === 0);
}),
editorOrigVal = editor.getValue(),
nth = 0,
// I might simplify this regex for my particular use case.
newMarkdown = editorOrigVal.replace(/^(?:\{<(.*?)>\})?!(?:\[([^\n\]]*)\])(:\(([^\n\]]*)\))?$/gim, function (match, i, original) {
nth++;
return (nth === (index + 1)) ? (match + "(" + response.path + ")") : match;
});
editor.setValue(newMarkdown);
holderP.classList.remove("dropzone");
holderP.innerHTML = '<img src="' + response.path + '"/>';
}
});
}
);
}
function updatePreview() {
preview.innerHTML = converter.makeHtml(editor.getValue());
updateImagePlaceholders();
}
function updateWordCount() {
var text = document.querySelector('#ghostdown-rendered').innerHTML.replace(/(<([^>]+)>)/ig, '').trim(),
count = (text.length === 0) ? 0 : text.split(/\s+/).length;
document.querySelector('#ghostdown-wordcount').innerHTML = count + " words";
}
function syncScroll() {
var codeViewport = document.querySelector('.CodeMirror-scroll'),
previewViewport = document.querySelector('#ghostdown-preview'),
codeContent = document.querySelector('.CodeMirror-sizer'),
previewContent = document.querySelector('#ghostdown-rendered'),
codeHeight = codeContent.clientHeight - window.getComputedStyle(codeViewport, null).height.split("px")[0],
// TODO: for some reason, this needs a 50px 'bodge' to make it scroll to the bottom properly
previewHeight = previewContent.clientHeight - window.getComputedStyle(previewViewport, null).height.split("px")[0] + 50,
ratio = previewHeight / codeHeight,
previewPostition = codeViewport.scrollTop * ratio;
previewViewport.scrollTop = previewPostition;
}
editor.on("change", function () {
updatePreview();
syncScroll();
updateWordCount();
});
updatePreview();
updateWordCount();
document.querySelector('.CodeMirror-scroll').onscroll = syncScroll;
});
<!-- Dropzone imports (although we won't be using dropzone) and ghostdown.js -->
<form action="/article/save" method="post">
<div class="ghostdown-container mdl-grid">
<div class="ghostdown-panel mdl-color--white mdl-shadow--4dp content mdl-color-text--grey-800 mdl-cell mdl-cell--6-col">
<h2 class="editor-title">Markdown</h2>
<textarea name="content" id="ghostdown-editor"># Ghostdown
This is an attempt to clone the [Ghost](http://ghost.org) blogging platform's editor. The idea is simple - write your content using the simple [Markdown](http://daringfireball.net/projects/markdown/) format, and see a live preview at the same time.
![Write with a coffee](https://as1.ftcdn.net/jpg/00/69/28/76/500_F_69287640_oKhSm7NcuIb3miPyx64LU4veg6US0x3e.jpg)
## Usage
The `index.html` file contains a usage example with styling and layout, but it's really very easy. Simply download the [repository](https://github.com/thlabs/ghostdown) (or just the `scripts\ghostdown.js` file). *Ghostdown* is setup to use [Require.js](http://requirejs.org), so include the following line in your html file:
```html
<script type="text/javascript" data-main="scripts/ghostdown" src="https://cdnjs.cloudflare.com/ajax/libs/require.js/2.1.20/require.min.js"></script>
```
### Image uploading
![Placeholder for image]
## Libraries
*Ghostdown* is built on the following existing libraries:
* [CodeMirror](http://codemirror.net) - for editing and syntax highlighting of the Markdown content
* [showdown](https://github.com/showdownjs/showdown) - to translate the markdown into the live preview
* [Dropzone](http://www.dropzonejs.com/) - to simplify image uploading
### Credits
Based on some initial work by [Shawn Xie](https://github.com/fengluo/ghostdown)</textarea>
</div>
<div class="ghostdown-panel mdl-color--white mdl-shadow--4dp content mdl-color-text--grey-800 mdl-cell mdl-cell--6-col">
<h2 class="editor-title">Preview</h2>
<span class="editor-title" id="ghostdown-wordcount">0 Words</span>
<div id="ghostdown-preview">
<div id="ghostdown-rendered"></div>
</div>
</div>
<footer class="ghostdown-footer mdl-mini-footer">
<div class="mdl-mini-footer--left-section">
&copy;2015 THLabs.net&nbsp;&nbsp;|&nbsp;&nbsp;Powered by <a href="https://github.com/THLabs/ghostdown">ghostdown</a>
</div>
<div class="mdl-mini-footer--right-section">
<button class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored mdl-button--accent mdl-button--ripple-effect ghostdown-button-save" type="submit">Save</button>
</div>
</footer>
</div>
</form>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment