Skip to content

Commit

Permalink
Allow selecting static files in MarkdownEditor (#565)
Browse files Browse the repository at this point in the history
This adds a new button to the MarkdownEditor (with icon `paperclip`). Clicking the button triggers the staticfilepicker modal.
On selecting a file the editor gets updated depending on the file extension,
If an image file was selected, *Markdown image tag* is inserted else a *Markdown link tag* is inserted.

Irrespective of the file-type selected, the injected Markdown includes **the static-file's relative_path enclosed in a Liquid object with Jekyll's `relative_url` filter**.
For example, if static-file `assets/logo.png` is selected, then the inserted Markdown will be:
```
![]({{ 'assets/logo.png' | relative_url }})
```
  • Loading branch information
ashmaroli committed Mar 1, 2020
1 parent ea81df9 commit 3defeb1
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 5 deletions.
56 changes: 52 additions & 4 deletions src/components/MarkdownEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import SimpleMDE from 'simplemde';
import hljs from '../utils/highlighter';
import FilePicker from './FilePicker';
import { getExtensionFromPath } from '../utils/helpers';

const classNames = [
'editor-toolbar',
Expand Down Expand Up @@ -38,6 +40,9 @@ class MarkdownEditor extends Component {
opts['renderingConfig'] = {
codeSyntaxHighlighting: true,
};
opts['insertTexts'] = {
image: ['![', '](#url#)'],
};
let toolbarIcons = [
'bold',
'italic',
Expand All @@ -51,6 +56,12 @@ class MarkdownEditor extends Component {
'link',
'image',
'table',
{
name: 'filepicker',
action: () => this.refs.filepicker.refs.trigger.click(),
className: 'fa fa-paperclip',
title: 'Insert Static File',
},
'|',
'preview',
'side-by-side',
Expand Down Expand Up @@ -84,11 +95,48 @@ class MarkdownEditor extends Component {
}
}

// Adapted from an internal helper function within SimpleMDE package.
_replaceSelectedText = (cm, headNTail, url) => {
const startPoint = cm.getCursor('start');
const endPoint = cm.getCursor('end');
const text = cm.getSelection();

let [head, tail] = headNTail;
if (url) {
tail = tail.replace('#url#', url);
}

cm.replaceSelection(`${head}${text}${tail}`);
startPoint.ch += head.length;

if (startPoint !== endPoint) {
endPoint.ch += head.length;
}

cm.setSelection(startPoint, endPoint);
cm.focus();
};

handleFilePick = path => {
const { codemirror, getState, options } = this.editor;
const { image, link } = options.insertTexts;
const url = `{{ '${path}' | relative_url }}`;
const ext = getExtensionFromPath(path);

const type = /png|jpg|gif|jpeg|svg|ico/i.test(ext) ? image : link;
this._replaceSelectedText(codemirror, type, url);
};

render() {
return React.createElement(
'div',
{ ref: 'container' },
React.createElement('textarea', { ref: 'text' })
return (
<div>
<div style={{ display: 'none' }}>
<FilePicker ref="filepicker" onPick={this.handleFilePick} />
</div>
<div ref="container">
<textarea ref="text" />
</div>
</div>
);
}
}
Expand Down
25 changes: 24 additions & 1 deletion src/components/tests/__snapshots__/markdowneditor.spec.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

exports[`Components::MarkdownEditor renders correctly 1`] = `
<div>
<textarea />
<div
style={
Object {
"display": "none",
}
}
>
<span
className="images-wrapper"
>
<button
onClick={[Function]}
>
<i
aria-hidden="true"
className="fa fa-picture-o"
/>
</button>
<noscript />
</span>
</div>
<div>
<textarea />
</div>
</div>
`;
37 changes: 37 additions & 0 deletions src/components/tests/markdowneditor.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import renderer from 'react-test-renderer';
import { mount } from 'enzyme';
import MarkdownEditor from '../MarkdownEditor';
import FilePicker from '../FilePicker';

const props = {
onChange: f => f,
Expand All @@ -10,12 +12,47 @@ const props = {
initialValue: '',
};

function setup() {
const component = mount(<MarkdownEditor {...props} />);
const { editor } = component.nodes[0];
return {
component,
editor,
cm: editor.codemirror,
};
}

describe('Components::MarkdownEditor', () => {
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<MarkdownEditor {...props} />, div);
});

it('allows selecting static-file', () => {
const { component, cm } = setup();

component.node._replaceSelectedText = jest.fn();
cm.replaceSelection = jest.fn();
cm.setSelection = jest.fn();
cm.focus = jest.fn();

const { _replaceSelectedText } = component.node;

component.find(FilePicker).prop('onPick')('foo.png');
expect(_replaceSelectedText).lastCalledWith(
cm,
['![', '](#url#)', '![](', '#url#)'],
"{{ 'foo.png' | relative_url }}"
);

component.find(FilePicker).prop('onPick')('bar.txt');
expect(_replaceSelectedText).lastCalledWith(
cm,
['[', '](#url#)'],
"{{ 'bar.txt' | relative_url }}"
);
});

it('renders correctly', () => {
const tree = renderer.create(<MarkdownEditor {...props} />).toJSON();
expect(tree).toMatchSnapshot();
Expand Down

0 comments on commit 3defeb1

Please sign in to comment.