diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 1b0e286ed..917b3b5cc 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -21,12 +21,11 @@ import { length } from 'stringz'; import { countableText } from '../util/counter'; import Icon from 'mastodon/components/icon'; import snarkdown from 'snarkdown'; - +import DOMPurify from 'dompurify'; // import elephantUIPlane from '../../../images/elephant_ui_plane.svg'; // import { mascot } from '../../initial_state'; - const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d'; const messages = defineMessages({ @@ -39,35 +38,38 @@ const messages = defineMessages({ export default @injectIntl class ComposeForm extends ImmutablePureComponent { + textMarkdownConverted = ''; + static contextTypes = { router: PropTypes.object, }; static propTypes = { - intl : PropTypes.object.isRequired, - text : PropTypes.string.isRequired, - suggestions : ImmutablePropTypes.list, - spoiler : PropTypes.bool, - privacy : PropTypes.string, - spoilerText : PropTypes.string, - focusDate : PropTypes.instanceOf(Date), - caretPosition : PropTypes.number, - maxTootCharsLimit : PropTypes.number, - preselectDate : PropTypes.instanceOf(Date), - isSubmitting : PropTypes.bool, - isChangingUpload : PropTypes.bool, - isUploading : PropTypes.bool, - onChange : PropTypes.func.isRequired, - onSubmit : PropTypes.func.isRequired, - onClearSuggestions : PropTypes.func.isRequired, - onFetchSuggestions : PropTypes.func.isRequired, - onSuggestionSelected: PropTypes.func.isRequired, - onChangeSpoilerText : PropTypes.func.isRequired, - onPaste : PropTypes.func.isRequired, - onPickEmoji : PropTypes.func.isRequired, - showSearch : PropTypes.bool, - anyMedia : PropTypes.bool, - singleColumn : PropTypes.bool, + intl : PropTypes.object.isRequired, + text : PropTypes.string.isRequired, + textMarkdownConverted: PropTypes.string, + suggestions : ImmutablePropTypes.list, + spoiler : PropTypes.bool, + privacy : PropTypes.string, + spoilerText : PropTypes.string, + focusDate : PropTypes.instanceOf(Date), + caretPosition : PropTypes.number, + maxTootCharsLimit : PropTypes.number, + preselectDate : PropTypes.instanceOf(Date), + isSubmitting : PropTypes.bool, + isChangingUpload : PropTypes.bool, + isUploading : PropTypes.bool, + onChange : PropTypes.func.isRequired, + onSubmit : PropTypes.func.isRequired, + onClearSuggestions : PropTypes.func.isRequired, + onFetchSuggestions : PropTypes.func.isRequired, + onSuggestionSelected : PropTypes.func.isRequired, + onChangeSpoilerText : PropTypes.func.isRequired, + onPaste : PropTypes.func.isRequired, + onPickEmoji : PropTypes.func.isRequired, + showSearch : PropTypes.bool, + anyMedia : PropTypes.bool, + singleColumn : PropTypes.bool, }; static defaultProps = { @@ -80,14 +82,18 @@ class ComposeForm extends ImmutablePureComponent { }; handleKeyDown = (e) => { + let markdownConverted = this.convertMarkdown(this.props.text); + console.log('markdownConverted', markdownConverted); + this.render(); if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { this.handleSubmit(); } + }; getFulltextForCharacterCounting = () => { - return [this.props.spoiler? this.props.spoilerText: '', countableText(this.props.text)].join(''); - } + return [this.props.spoiler ? this.props.spoilerText : '', countableText(this.props.text)].join(''); + }; canSubmit = () => { const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props; @@ -95,7 +101,7 @@ class ComposeForm extends ImmutablePureComponent { const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0; return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia)); - } + }; handleSubmit = () => { if (this.props.text !== this.autosuggestTextarea.textarea.value) { @@ -193,13 +199,39 @@ class ComposeForm extends ImmutablePureComponent { this.props.onPickEmoji(position, data, needsSpace); }; - convertMarkdown(){ - let md = '_this_ is **easy** to `use`.'; - let html = snarkdown(md); - console.log(html); + convertMarkdown(markdownInput) { + let md = `# title ouane +* aaaaaa +* bbbbbb +* cccccc +aaaaa + +## title deux +deuuuux + +### title trois + +_this_ is **easy** to \` use\`. + +* aaaaaa +* bbbbbb +* cccccc + +un lien vers [[www.wikipedia.org]] +[un lien vers cipherbliss.com](https://cipherbliss.com) + `; + let html = ''; + if (!markdownInput) { + html = snarkdown(md); + } else { + html = snarkdown(markdownInput); + html = DOMPurify.sanitize(markdownInput); + } + this.textMarkdownConverted = html; + return html; }; - render () { + render() { const { intl, onPaste, showSearch } = this.props; const disabled = this.props.isSubmitting; let publishText = ''; @@ -210,7 +242,6 @@ class ComposeForm extends ImmutablePureComponent { } else { publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish); } - this.convertMarkdown(); return (
@@ -267,15 +298,38 @@ class ComposeForm extends ImmutablePureComponent { -
-
- + +
+
-
-
- +
+ + + {this.textMarkdownConverted && + +
+

Markdown:

+
+
+
+ + } + + ); - } + }; } diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index 37a0e8845..d94ae68ea 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -13,6 +13,7 @@ import { const mapStateToProps = state => ({ text: state.getIn(['compose', 'text']), + textMarkdownConverted: state.getIn(['compose', 'textMarkdownConverted']), suggestions: state.getIn(['compose', 'suggestions']), spoiler: state.getIn(['compose', 'spoiler']), spoilerText: state.getIn(['compose', 'spoiler_text']), diff --git a/app/javascript/styles/bliss.scss b/app/javascript/styles/bliss.scss index c5dc571f6..4d3f5ab7b 100644 --- a/app/javascript/styles/bliss.scss +++ b/app/javascript/styles/bliss.scss @@ -15,6 +15,7 @@ @import 'bliss/compact_header'; @import 'bliss/widgets'; @import 'bliss/forms'; +@import 'bliss/markdown'; @import 'bliss/accounts'; @import 'bliss/statuses'; @import 'bliss/boost'; diff --git a/app/javascript/styles/bliss/_markdown.scss b/app/javascript/styles/bliss/_markdown.scss new file mode 100644 index 000000000..62d69fb46 --- /dev/null +++ b/app/javascript/styles/bliss/_markdown.scss @@ -0,0 +1,56 @@ +.formatted-markdown{ + padding: 1em; + &.preview{ + + background: $dark-text-color; + margin-top: 0.5em; + } + + h1,h2,h3,h4,h5,h6{ + &:first-letter{ + text-transform: uppercase; + } + } + h1{ + font-size: 1.5rem; + margin-bottom: 1em; + margin-top: 1.5em; + font-weight: 500; + } + h2{ + font-size: 1.25rem; + margin-bottom: 1em; + margin-top: 1.25em; + font-weight: 400; + } + h3{ + font-size: 1rem; + margin-bottom: 1em; + font-weight: 300; + } + h4{ + font-size: 0.85rem; + margin-bottom: 1em; + } + h5{ + font-size: 0.75rem; + margin-bottom: 1em; + } + h6{ + font-size: 0.70rem; + margin-bottom: 1em; + } + strong{ + font-weight:700; + } + i{ + font-style: italic; + } + a { + color: $primary-text-color; + } + em{ + background: $darker-text-color; + padding: 0.25em; + } +} diff --git a/package.json b/package.json index 3257e8e3b..f785145e3 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "css-loader": "^5.0.1", "cssnano": "^4.1.10", "detect-passive-events": "^2.0.2", + "dompurify": "^2.2.6", "dotenv": "^8.2.0", "emoji-mart": "Gargron/emoji-mart#build", "es6-symbol": "^3.1.3", diff --git a/yarn.lock b/yarn.lock index 34ffa6813..0367e3955 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3956,6 +3956,11 @@ domexception@^2.0.1: dependencies: webidl-conversions "^5.0.0" +dompurify@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.6.tgz#54945dc5c0b45ce5ae228705777e8e59d7b2edc4" + integrity sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ== + domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a"