Unverified Commit fc3e28d8 authored by Robert Knight's avatar Robert Knight Committed by GitHub

Merge pull request #1438 from hypothesis/convert-markdown-view

Convert markdown editor 2/3 - Convert markdown viewer
parents 637a72f0 8aa53d03
'use strict';
const classnames = require('classnames');
const { createElement } = require('preact');
const { useEffect, useMemo, useRef } = require('preact/hooks');
const propTypes = require('prop-types');
const { replaceLinksWithEmbeds } = require('../media-embedder');
const renderMarkdown = require('../render-markdown');
/**
* A component which renders markdown as HTML and replaces recognized links
* with embedded video/audio.
*/
function MarkdownView({ markdown = '', textClass = {} }) {
const html = useMemo(() => (markdown ? renderMarkdown(markdown) : ''), [
markdown,
]);
const content = useRef(null);
useEffect(() => {
replaceLinksWithEmbeds(content.current);
}, [markdown]);
return (
<div
className={classnames('markdown-view', textClass)}
ref={content}
dangerouslySetInnerHTML={{ __html: html }}
/>
);
}
MarkdownView.propTypes = {
/** The string of markdown to display. */
markdown: propTypes.string,
/**
* A CSS classname-to-boolean map of classes to apply to the container of
* the rendered markdown.
*/
textClass: propTypes.object,
};
module.exports = MarkdownView;
......@@ -160,6 +160,12 @@ describe('annotation', function() {
})
.component('markdown', {
bindings: require('../markdown').bindings,
})
.component('markdownView', {
bindings: {
markdown: '<',
textClass: '<',
},
});
});
......@@ -1268,9 +1274,9 @@ describe('annotation', function() {
function(testCase) {
const el = createDirective(testCase.ann).element;
assert.match(
el.find('markdown').controller('markdown'),
el.find('markdown-view').controller('markdownView'),
sinon.match({
customTextClass: testCase.textClass,
textClass: testCase.textClass,
})
);
},
......
'use strict';
const { createElement } = require('preact');
const { mount } = require('enzyme');
const MarkdownView = require('../markdown-view');
describe('MarkdownView', () => {
let fakeMediaEmbedder;
let fakeRenderMarkdown;
beforeEach(() => {
fakeRenderMarkdown = markdown => `rendered:${markdown}`;
fakeMediaEmbedder = {
replaceLinksWithEmbeds: el => {
// Tag the element as having been processed
el.dataset.replacedLinksWithEmbeds = 'yes';
},
};
MarkdownView.$imports.$mock({
'../render-markdown': fakeRenderMarkdown,
'../media-embedder': fakeMediaEmbedder,
});
});
afterEach(() => {
MarkdownView.$imports.$restore();
});
it('renders nothing if no markdown is provied', () => {
const wrapper = mount(<MarkdownView />);
assert.equal(wrapper.text(), '');
});
it('renders markdown as HTML', () => {
const wrapper = mount(<MarkdownView markdown="**test**" />);
const rendered = wrapper.find('.markdown-view').getDOMNode();
assert.equal(rendered.innerHTML, 'rendered:**test**');
});
it('re-renders markdown after an update', () => {
const wrapper = mount(<MarkdownView markdown="**test**" />);
wrapper.setProps({ markdown: '_updated_' });
const rendered = wrapper.find('.markdown-view').getDOMNode();
assert.equal(rendered.innerHTML, 'rendered:_updated_');
});
it('replaces links with embeds in rendered output', () => {
const wrapper = mount(<MarkdownView markdown="**test**" />);
const rendered = wrapper.find('.markdown-view').getDOMNode();
assert.equal(rendered.dataset.replacedLinksWithEmbeds, 'yes');
});
it('applies `textClass` class to container', () => {
const wrapper = mount(
<MarkdownView markdown="foo" textClass={{ 'fancy-effect': true }} />
);
assert.isTrue(wrapper.find('.markdown-view.fancy-effect').exists());
});
});
......@@ -162,6 +162,10 @@ function startAngularApp(config) {
wrapReactComponent(require('./components/logged-out-message'))
)
.component('markdown', require('./components/markdown'))
.component(
'markdownView',
wrapReactComponent(require('./components/markdown-view'))
)
.component(
'moderationBanner',
wrapReactComponent(require('./components/moderation-banner'))
......
......@@ -39,11 +39,11 @@
overflow-hysteresis="20"
content-data="vm.state().text"
ng-if="!vm.editing()">
<markdown text="vm.state().text"
custom-text-class="{'annotation-body is-hidden':vm.isHiddenByModerator(),
'has-content':vm.hasContent()}"
read-only="true">
</markdown>
<markdown-view
markdown="vm.state().text"
text-class="{'annotation-body is-hidden':vm.isHiddenByModerator(),
'has-content':vm.hasContent()}"
</markdown-view>
</excerpt>
<markdown text="vm.state().text"
on-edit-text="vm.setText(text)"
......
.markdown-view {
@include styled-text;
cursor: text;
// Prevent long URLs etc. in body causing overflow
overflow-wrap: break-word;
// Margin between bottom of ascent of username and top of
// x-height of annotation-body should be ~15px.
// Remove additional margin-top added by the first p within
// the annotation-body
p:first-child {
margin-top: 0;
}
// Margin between bottom of ascent of annotation-body and top of
// ascent of annotation-footer should be ~15px in threaded-replies
// and 20px in the top level annotation.
// Remove additional margin-bottom added by the last p within
// the annotation-body
p:last-child {
margin-bottom: 1px;
}
}
......@@ -33,6 +33,7 @@ $base-line-height: 20px;
@import './components/help-panel';
@import './components/logged-out-message';
@import './components/markdown';
@import './components/markdown-view';
@import './components/menu';
@import './components/menu-item';
@import './components/menu-section';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment