Rich Text Editor (WIP)
This commit is contained in:
parent
eb89d552ce
commit
20a3221c4b
@ -263,7 +263,7 @@ export function submitCompose(group, replyToId = null, router, isStandalone) {
|
|||||||
if (!me) return;
|
if (!me) return;
|
||||||
|
|
||||||
let status = getState().getIn(['compose', 'text'], '');
|
let status = getState().getIn(['compose', 'text'], '');
|
||||||
const markdown = getState().getIn(['compose', 'markdown'], '');
|
let markdown = getState().getIn(['compose', 'markdown'], '');
|
||||||
const media = getState().getIn(['compose', 'media_attachments']);
|
const media = getState().getIn(['compose', 'media_attachments']);
|
||||||
|
|
||||||
// : hack :
|
// : hack :
|
||||||
@ -276,10 +276,13 @@ export function submitCompose(group, replyToId = null, router, isStandalone) {
|
|||||||
}
|
}
|
||||||
return hasProtocol ? match : `http://${match}`
|
return hasProtocol ? match : `http://${match}`
|
||||||
})
|
})
|
||||||
// markdown = statusMarkdown.replace(urlRegex, (match) =>{
|
markdown = markdown.replace(urlRegex, (match) =>{
|
||||||
// const hasProtocol = match.startsWith('https://') || match.startsWith('http://')
|
const hasProtocol = match.startsWith('https://') || match.startsWith('http://')
|
||||||
// return hasProtocol ? match : `http://${match}`
|
if (!hasProtocol) {
|
||||||
// })
|
if (status.indexOf(`@${match}`) > -1) return match
|
||||||
|
}
|
||||||
|
return hasProtocol ? match : `http://${match}`
|
||||||
|
})
|
||||||
|
|
||||||
const inReplyToId = getState().getIn(['compose', 'in_reply_to'], null) || replyToId
|
const inReplyToId = getState().getIn(['compose', 'in_reply_to'], null) || replyToId
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import escapeTextContentForBrowser from 'escape-html';
|
import escapeTextContentForBrowser from 'escape-html'
|
||||||
import emojify from '../../components/emoji/emoji';
|
import { markdownToDraft } from 'markdown-draft-js'
|
||||||
import { unescapeHTML } from '../../utils/html';
|
import { Remarkable } from 'remarkable'
|
||||||
import { expandSpoilers } from '../../initial_state';
|
import * as entities from 'entities'
|
||||||
|
import emojify from '../../components/emoji/emoji'
|
||||||
|
import { unescapeHTML } from '../../utils/html'
|
||||||
|
import { expandSpoilers } from '../../initial_state'
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
const domParser = new DOMParser()
|
||||||
|
|
||||||
const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
|
const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
|
||||||
obj[`:${emoji.shortcode}:`] = emoji;
|
obj[`:${emoji.shortcode}:`] = emoji;
|
||||||
@ -63,8 +66,40 @@ export function normalizeStatus(status, normalOldStatus) {
|
|||||||
const spoilerText = normalStatus.spoiler_text || '';
|
const spoilerText = normalStatus.spoiler_text || '';
|
||||||
const searchContent = [spoilerText, status.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
const searchContent = [spoilerText, status.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
|
||||||
const emojiMap = makeEmojiMap(normalStatus);
|
const emojiMap = makeEmojiMap(normalStatus);
|
||||||
const theContent = !!normalStatus.rich_content ? normalStatus.rich_content : normalStatus.content;
|
|
||||||
|
let theContent
|
||||||
|
if (!!normalStatus.rich_content) {
|
||||||
|
theContent = normalStatus.rich_content
|
||||||
|
// let rawObject = markdownToDraft(theContent, {
|
||||||
|
// preserveNewlines: true,
|
||||||
|
// remarkablePreset: 'commonmark',
|
||||||
|
// remarkableOptions: {
|
||||||
|
// enable: {
|
||||||
|
// inline: ['del', 'ins'],
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
|
const md = new Remarkable({
|
||||||
|
html: false,
|
||||||
|
breaks: true,
|
||||||
|
})
|
||||||
|
let html = md.render(theContent)
|
||||||
|
html = entities.decodeHTML(html)
|
||||||
|
|
||||||
|
theContent = html
|
||||||
|
|
||||||
|
console.log("html:", html)
|
||||||
|
console.log("theContent:", theContent)
|
||||||
|
console.log("status:", status)
|
||||||
|
console.log("normalStatus:", normalStatus)
|
||||||
|
// console.log("rawObject:", rawObject)
|
||||||
|
} else {
|
||||||
|
theContent = normalStatus.content
|
||||||
|
}
|
||||||
|
// let theContent = !!normalStatus.rich_content ? normalStatus.rich_content : normalStatus.content;
|
||||||
|
|
||||||
|
|
||||||
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
|
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
|
||||||
normalStatus.contentHtml = emojify(theContent, emojiMap, false, true);
|
normalStatus.contentHtml = emojify(theContent, emojiMap, false, true);
|
||||||
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
|
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
|
||||||
@ -86,3 +121,21 @@ export function normalizePoll(poll) {
|
|||||||
|
|
||||||
return normalPoll;
|
return normalPoll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// <p><h1>attention!</h1></p>
|
||||||
|
// <p>#test @bob #nice https://bob.com http://techcrunch.com <del>strike it</del></p>
|
||||||
|
// <p><del>https://twitter.com</del></p>
|
||||||
|
// <p><em>@bobitalic</em></p>
|
||||||
|
// <p><pre><code>jonincode</code></pre></p>
|
||||||
|
|
||||||
|
// # attention!
|
||||||
|
// #test @bob #nice https://bob.com http://techcrunch.com ~~strike it~~
|
||||||
|
|
||||||
|
// ~~https://twitter.com~~
|
||||||
|
|
||||||
|
// _@bobitalic_
|
||||||
|
|
||||||
|
// ```
|
||||||
|
// jonincode
|
||||||
|
// ```
|
@ -109,6 +109,7 @@ export function fetchStatus(id) {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
dispatch(fetchStatusSuccess(skipLoading));
|
dispatch(fetchStatusSuccess(skipLoading));
|
||||||
}, () => api(getState).get(`/api/v1/statuses/${id}`).then(response => {
|
}, () => api(getState).get(`/api/v1/statuses/${id}`).then(response => {
|
||||||
|
console.log("response.data:", response.data)
|
||||||
dispatch(importFetchedStatus(response.data));
|
dispatch(importFetchedStatus(response.data));
|
||||||
dispatch(fetchStatusSuccess(skipLoading));
|
dispatch(fetchStatusSuccess(skipLoading));
|
||||||
})).catch(error => {
|
})).catch(error => {
|
||||||
|
@ -53,11 +53,12 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||||||
tokenStart: 0,
|
tokenStart: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = (e, value, selectionStart, markdown) => {
|
onChange = (e, value, markdown, selectionStart) => {
|
||||||
if (!isObject(e)) {
|
if (!isObject(e)) {
|
||||||
e = {
|
e = {
|
||||||
target: {
|
target: {
|
||||||
value,
|
value,
|
||||||
|
markdown,
|
||||||
selectionStart,
|
selectionStart,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -65,8 +66,6 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||||||
|
|
||||||
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart, this.props.searchTokens);
|
const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart, this.props.searchTokens);
|
||||||
|
|
||||||
// console.log('onChange', e.target.value, e.target, this.textbox, tokenStart, token)
|
|
||||||
|
|
||||||
if (token !== null && this.state.lastToken !== token) {
|
if (token !== null && this.state.lastToken !== token) {
|
||||||
this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
|
this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart });
|
||||||
this.props.onSuggestionsFetchRequested(token);
|
this.props.onSuggestionsFetchRequested(token);
|
||||||
@ -75,7 +74,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||||||
this.props.onSuggestionsClearRequested();
|
this.props.onSuggestionsClearRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.props.onChange(e, markdown);
|
this.props.onChange(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown = (e) => {
|
onKeyDown = (e) => {
|
||||||
@ -259,7 +258,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className={textareaContainerClasses}>
|
<div className={textareaContainerClasses}>
|
||||||
<Textarea
|
{/*<Textarea
|
||||||
inputRef={this.setTextbox}
|
inputRef={this.setTextbox}
|
||||||
className={textareaClasses}
|
className={textareaClasses}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
@ -273,9 +272,9 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
onPaste={this.onPaste}
|
onPaste={this.onPaste}
|
||||||
aria-autocomplete='list'
|
aria-autocomplete='list'
|
||||||
/>
|
/>*/}
|
||||||
|
|
||||||
{/*<Composer
|
<Composer
|
||||||
inputRef={this.setTextbox}
|
inputRef={this.setTextbox}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
@ -288,7 +287,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||||||
onBlur={this.onBlur}
|
onBlur={this.onBlur}
|
||||||
onPaste={this.onPaste}
|
onPaste={this.onPaste}
|
||||||
small={small}
|
small={small}
|
||||||
/>*/}
|
/>
|
||||||
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
@ -8,7 +8,6 @@ import {
|
|||||||
ContentState,
|
ContentState,
|
||||||
} from 'draft-js'
|
} from 'draft-js'
|
||||||
import { draftToMarkdown } from 'markdown-draft-js'
|
import { draftToMarkdown } from 'markdown-draft-js'
|
||||||
// import draftToMarkdown from 'draftjs-to-markdown'
|
|
||||||
import { urlRegex } from '../features/ui/util/url_regex'
|
import { urlRegex } from '../features/ui/util/url_regex'
|
||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
import RichTextEditorBar from './rich_text_editor_bar'
|
import RichTextEditorBar from './rich_text_editor_bar'
|
||||||
@ -76,7 +75,6 @@ const compositeDecorator = new CompositeDecorator([
|
|||||||
const HANDLE_REGEX = /\@[\w]+/g;
|
const HANDLE_REGEX = /\@[\w]+/g;
|
||||||
const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;
|
const HASHTAG_REGEX = /\#[\w\u0590-\u05ff]+/g;
|
||||||
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
|
||||||
})
|
})
|
||||||
@ -117,7 +115,6 @@ class Composer extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
// console.log("this.props.value:", this.props.value)
|
|
||||||
if (prevProps.value !== this.props.value) {
|
if (prevProps.value !== this.props.value) {
|
||||||
// const editorState = EditorState.push(this.state.editorState, ContentState.createFromText(this.props.value));
|
// const editorState = EditorState.push(this.state.editorState, ContentState.createFromText(this.props.value));
|
||||||
// this.setState({ editorState })
|
// this.setState({ editorState })
|
||||||
@ -126,24 +123,32 @@ class Composer extends PureComponent {
|
|||||||
|
|
||||||
// EditorState.createWithContent(ContentState.createFromText('Hello'))
|
// EditorState.createWithContent(ContentState.createFromText('Hello'))
|
||||||
|
|
||||||
onChange = (editorState) => {
|
onChange = (editorState, b, c, d) => {
|
||||||
this.setState({ editorState })
|
this.setState({ editorState })
|
||||||
const content = this.state.editorState.getCurrentContent();
|
|
||||||
|
const content = editorState.getCurrentContent();
|
||||||
const text = content.getPlainText('\u0001')
|
const text = content.getPlainText('\u0001')
|
||||||
|
|
||||||
// const selectionState = editorState.getSelection()
|
const selectionState = editorState.getSelection()
|
||||||
// const selectionStart = selectionState.getStartOffset()
|
const selectionStart = selectionState.getStartOffset()
|
||||||
|
|
||||||
// const rawObject = convertToRaw(content);
|
const rawObject = convertToRaw(content);
|
||||||
// const markdownString = draftToMarkdown(rawObject);
|
const markdownString = draftToMarkdown(rawObject, {
|
||||||
// const markdownString = draftToMarkdown(rawObject, {
|
preserveNewlines: true,
|
||||||
// trigger: '#',
|
remarkablePreset: 'commonmark',
|
||||||
// separator: ' ',
|
remarkableOptions: {
|
||||||
// });
|
disable: {
|
||||||
|
block: ['table']
|
||||||
|
},
|
||||||
|
enable: {
|
||||||
|
inline: ['del', 'ins'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// console.log("text:", text, this.props.value)
|
console.log("text:", markdownString)
|
||||||
|
|
||||||
this.props.onChange(null, text, selectionStart, markdownString)
|
this.props.onChange(null, text, markdownString, selectionStart)
|
||||||
}
|
}
|
||||||
|
|
||||||
// **bold**
|
// **bold**
|
||||||
@ -219,7 +224,7 @@ class Composer extends PureComponent {
|
|||||||
return (
|
return (
|
||||||
<div className={_s.default}>
|
<div className={_s.default}>
|
||||||
|
|
||||||
{ /** : todo : */
|
{
|
||||||
!small &&
|
!small &&
|
||||||
<RichTextEditorBar
|
<RichTextEditorBar
|
||||||
editorState={editorState}
|
editorState={editorState}
|
||||||
@ -241,6 +246,9 @@ class Composer extends PureComponent {
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
readOnly={disabled}
|
readOnly={disabled}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onFocus={onFocus}
|
||||||
|
stripPastedStyles
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,6 +21,7 @@ import PollButton from './poll_button'
|
|||||||
import PollForm from './poll_form'
|
import PollForm from './poll_form'
|
||||||
import SchedulePostButton from './schedule_post_button'
|
import SchedulePostButton from './schedule_post_button'
|
||||||
import SpoilerButton from './spoiler_button'
|
import SpoilerButton from './spoiler_button'
|
||||||
|
import RichTextEditorButton from './rich_text_editor_button'
|
||||||
import StatusContainer from '../../../containers/status_container'
|
import StatusContainer from '../../../containers/status_container'
|
||||||
import StatusVisibilityButton from './status_visibility_button'
|
import StatusVisibilityButton from './status_visibility_button'
|
||||||
import UploadButton from './media_upload_button'
|
import UploadButton from './media_upload_button'
|
||||||
@ -92,14 +93,8 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
showSearch: false,
|
showSearch: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleChange = (e, markdown) => {
|
handleChange = (e, selectionStart) => {
|
||||||
let position = null
|
this.props.onChange(e.target.value, e.target.markdown, this.props.replyToId, selectionStart)
|
||||||
try {
|
|
||||||
position = this.autosuggestTextarea.textbox.selectionStart
|
|
||||||
} catch (error) {
|
|
||||||
//
|
|
||||||
}
|
|
||||||
this.props.onChange(e.target.value, markdown, this.props.replyToId, position)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleComposeFocus = () => {
|
handleComposeFocus = () => {
|
||||||
@ -137,11 +132,11 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleSubmit = () => {
|
handleSubmit = () => {
|
||||||
if (this.props.text !== this.autosuggestTextarea.textbox.value) {
|
// if (this.props.text !== this.autosuggestTextarea.textbox.value) {
|
||||||
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
|
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
|
||||||
// Update the state to match the current text
|
// Update the state to match the current text
|
||||||
this.props.onChange(this.autosuggestTextarea.textbox.value);
|
// this.props.onChange(this.autosuggestTextarea.textbox.value);
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Submit disabled:
|
// Submit disabled:
|
||||||
const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
|
const { isSubmitting, isChangingUpload, isUploading, anyMedia } = this.props;
|
||||||
@ -218,6 +213,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleEmojiPick = (data) => {
|
handleEmojiPick = (data) => {
|
||||||
|
// : todo : with rich text
|
||||||
const { text } = this.props
|
const { text } = this.props
|
||||||
const position = this.autosuggestTextarea.textbox.selectionStart
|
const position = this.autosuggestTextarea.textbox.selectionStart
|
||||||
const needsSpace = data.custom && position > 0 && !ALLOWED_AROUND_SHORT_CODE.includes(text[position - 1])
|
const needsSpace = data.custom && position > 0 && !ALLOWED_AROUND_SHORT_CODE.includes(text[position - 1])
|
||||||
@ -474,7 +470,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
<StatusVisibilityButton />
|
<StatusVisibilityButton />
|
||||||
<SpoilerButton />
|
<SpoilerButton />
|
||||||
<SchedulePostButton />
|
<SchedulePostButton />
|
||||||
{ /* !shouldCondense && <RichTextEditorButton /> */}
|
<RichTextEditorButton />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
|
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
|
||||||
|
@ -120,6 +120,11 @@ body {
|
|||||||
overscroll-behavior-y: none;
|
overscroll-behavior-y: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
pre {
|
||||||
|
font-family: monospace !important;
|
||||||
|
}
|
||||||
|
|
||||||
.overflowYScroll {
|
.overflowYScroll {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@ -166,9 +171,16 @@ body {
|
|||||||
|
|
||||||
.statusContent ul,
|
.statusContent ul,
|
||||||
.statusContent ol {
|
.statusContent ol {
|
||||||
list-style-type: disc;
|
|
||||||
padding-left: 40px;
|
padding-left: 40px;
|
||||||
margin: 0;
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusContent ul {
|
||||||
|
/* list-style-type: disc; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.statusContent ol {
|
||||||
|
/* list-style-type: disc; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dangerousContent,
|
.dangerousContent,
|
||||||
@ -1002,6 +1014,7 @@ body {
|
|||||||
font-size: var(--fs_l);
|
font-size: var(--fs_l);
|
||||||
} */
|
} */
|
||||||
|
|
||||||
|
.statusContent blockquote,
|
||||||
:global(.RichEditor-blockquote) {
|
:global(.RichEditor-blockquote) {
|
||||||
border-left: 5px solid var(--border_color_secondary);
|
border-left: 5px solid var(--border_color_secondary);
|
||||||
color: var(--text_color_secondary);
|
color: var(--text_color_secondary);
|
||||||
@ -1010,11 +1023,14 @@ body {
|
|||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.statusContent pre,
|
||||||
:global(.public-DraftStyleDefault-pre) {
|
:global(.public-DraftStyleDefault-pre) {
|
||||||
background-color: rgba(0,0,0,.05);
|
background-color: rgba(0,0,0,.05);
|
||||||
font-family: 'Inconsolata', 'Menlo', 'Consolas', monospace;
|
font-family: 'Inconsolata', 'Menlo', 'Consolas', monospace;
|
||||||
font-size: var(--fs_l);
|
font-size: var(--fs_l);
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* */
|
/* */
|
||||||
|
@ -42,19 +42,6 @@ class Formatter
|
|||||||
|
|
||||||
html = encode_and_link_urls(html, linkable_accounts)
|
html = encode_and_link_urls(html, linkable_accounts)
|
||||||
|
|
||||||
# : todo :
|
|
||||||
if options[:use_markdown]
|
|
||||||
html = convert_headers(html)
|
|
||||||
html = convert_strong(html)
|
|
||||||
html = convert_italic(html)
|
|
||||||
html = convert_strikethrough(html)
|
|
||||||
html = convert_code(html)
|
|
||||||
html = convert_codeblock(html)
|
|
||||||
html = convert_links(html)
|
|
||||||
html = convert_lists(html)
|
|
||||||
html = convert_ordered_lists(html)
|
|
||||||
end
|
|
||||||
|
|
||||||
html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
|
html = encode_custom_emojis(html, status.emojis, options[:autoplay]) if options[:custom_emojify]
|
||||||
|
|
||||||
html = simple_format(html, {}, sanitize: false)
|
html = simple_format(html, {}, sanitize: false)
|
||||||
@ -321,79 +308,4 @@ class Formatter
|
|||||||
"<a data-focusable=\"true\" role=\"link\" href=\"#{encode(TagManager.instance.url_for(account))}\" class=\"u-url mention\">@#{encode(account.acct)}</a>"
|
"<a data-focusable=\"true\" role=\"link\" href=\"#{encode(TagManager.instance.url_for(account))}\" class=\"u-url mention\">@#{encode(account.acct)}</a>"
|
||||||
end
|
end
|
||||||
|
|
||||||
def convert_headers(html)
|
|
||||||
html.gsub(/^\#{1,6}.*$/) do |header|
|
|
||||||
weight = 0
|
|
||||||
header.split('').each do |char|
|
|
||||||
break unless char == '#'
|
|
||||||
weight += 1
|
|
||||||
end
|
|
||||||
content = header.sub(/^\#{1,6}/, '')
|
|
||||||
"<h#{weight}>#{content}</h#{weight}>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_strong(html)
|
|
||||||
html.gsub(/\*{2}.*\*{2}|_{2}.*_{2}/) do |strong|
|
|
||||||
content = strong.gsub(/\*{2}|_{2}/, '')
|
|
||||||
"<strong>#{content}</strong>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_italic(html)
|
|
||||||
html.gsub(/\*{1}(\w|\s)+\*{1}|_{1}(\w|\s)+_{1}/) do |italic|
|
|
||||||
content = italic.gsub(/\*{1}|_{1}/, '')
|
|
||||||
"<em>#{content}</em>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_strikethrough(html)
|
|
||||||
html.gsub(/~~(\w|\s)+~~/) do |strike|
|
|
||||||
content = strike.gsub(/~~/, '')
|
|
||||||
"<strike>#{content}</strike>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_code(html)
|
|
||||||
html.gsub(/`(\w|\s)+`/) do |code|
|
|
||||||
content = code.gsub(/`/, '')
|
|
||||||
"<code>#{content}</code>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_codeblock(html)
|
|
||||||
html.gsub(/```\w*(.*(\r\n|\r|\n))+```/) do |code|
|
|
||||||
lang = code.match(/```\w+/)[0].gsub(/`/, '')
|
|
||||||
content = code.gsub(/```\w+/, '```').gsub(/`/, '')
|
|
||||||
"<pre class=\"#{lang}\"><code>#{content}</code></pre>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_links(html)
|
|
||||||
html.gsub(/\[(\w|\s)+\]\((\w|\W)+\)/) do |anchor|
|
|
||||||
link_text = anchor.match(/\[(\w|\s)+\]/)[0].gsub(/[\[\]]/, '')
|
|
||||||
href = anchor.match(/\((\w|\W)+\)/)[0].gsub(/\(|\)/, '')
|
|
||||||
"<a href=\"#{href}\">#{link_text}</a>"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_lists(html)
|
|
||||||
html.gsub(/(\-.+(\r|\n|\r\n))+/) do |list|
|
|
||||||
items = "<ul>\n"
|
|
||||||
list.gsub(/\-.+/) do |li|
|
|
||||||
items << "<li>#{li.sub(/^\-/, '').strip}</li>\n"
|
|
||||||
end
|
|
||||||
items << "</ul>\n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def convert_ordered_lists(html)
|
|
||||||
html.gsub(/(\d\..+(\r|\n|\r\n))+/) do |list|
|
|
||||||
items = "<ol>\n"
|
|
||||||
list.gsub(/\d.+/) do |li|
|
|
||||||
items << "<li>#{li.sub(/^\d\./, '').strip}</li>\n"
|
|
||||||
end
|
|
||||||
items << "</ol>\n"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
@ -73,8 +73,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||||||
end
|
end
|
||||||
|
|
||||||
def rich_content
|
def rich_content
|
||||||
Formatter.instance.format(object).strip
|
Formatter.instance.format(object, use_markdown: true).strip
|
||||||
# Formatter.instance.format(object, use_markdown: true).strip
|
# raw_content = object.markdown
|
||||||
|
# return '' if raw_content.blank?
|
||||||
|
# raw_content.strip
|
||||||
end
|
end
|
||||||
|
|
||||||
def url
|
def url
|
||||||
|
@ -92,6 +92,7 @@
|
|||||||
"draft-js": "^0.11.4",
|
"draft-js": "^0.11.4",
|
||||||
"draftjs-to-markdown": "^0.6.0",
|
"draftjs-to-markdown": "^0.6.0",
|
||||||
"emoji-mart": "Gargron/emoji-mart#build",
|
"emoji-mart": "Gargron/emoji-mart#build",
|
||||||
|
"entities": "^2.0.3",
|
||||||
"es6-symbol": "^3.1.1",
|
"es6-symbol": "^3.1.1",
|
||||||
"escape-html": "^1.0.3",
|
"escape-html": "^1.0.3",
|
||||||
"exif-js": "^2.3.0",
|
"exif-js": "^2.3.0",
|
||||||
@ -153,6 +154,7 @@
|
|||||||
"redux": "^4.0.1",
|
"redux": "^4.0.1",
|
||||||
"redux-immutable": "^4.0.0",
|
"redux-immutable": "^4.0.0",
|
||||||
"redux-thunk": "^2.2.0",
|
"redux-thunk": "^2.2.0",
|
||||||
|
"remarkable": "^2.0.1",
|
||||||
"requestidlecallback": "^0.3.0",
|
"requestidlecallback": "^0.3.0",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
"rimraf": "^2.6.3",
|
"rimraf": "^2.6.3",
|
||||||
|
13
yarn.lock
13
yarn.lock
@ -3394,6 +3394,11 @@ entities@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
|
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.0.tgz#68d6084cab1b079767540d80e56a39b423e4abf4"
|
||||||
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
|
integrity sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==
|
||||||
|
|
||||||
|
entities@^2.0.3:
|
||||||
|
version "2.0.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/entities/-/entities-2.0.3.tgz#5c487e5742ab93c15abb5da22759b8590ec03b7f"
|
||||||
|
integrity sha512-MyoZ0jgnLvB2X3Lg5HqpFmn1kybDiIfEQmKzTb5apr51Rb+T3KdmMiqa70T+bhGnyv7bQ6WMj2QMHpGMmlrUYQ==
|
||||||
|
|
||||||
enzyme-adapter-react-16@^1.7.1:
|
enzyme-adapter-react-16@^1.7.1:
|
||||||
version "1.15.2"
|
version "1.15.2"
|
||||||
resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501"
|
resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501"
|
||||||
@ -8233,6 +8238,14 @@ remarkable@2.0.0:
|
|||||||
argparse "^1.0.10"
|
argparse "^1.0.10"
|
||||||
autolinker "^3.11.0"
|
autolinker "^3.11.0"
|
||||||
|
|
||||||
|
remarkable@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-2.0.1.tgz#280ae6627384dfb13d98ee3995627ca550a12f31"
|
||||||
|
integrity sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==
|
||||||
|
dependencies:
|
||||||
|
argparse "^1.0.10"
|
||||||
|
autolinker "^3.11.0"
|
||||||
|
|
||||||
remove-trailing-separator@^1.0.1:
|
remove-trailing-separator@^1.0.1:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user