diff options
Diffstat (limited to 'src/components/rich_content/rich_content.jsx')
| -rw-r--r-- | src/components/rich_content/rich_content.jsx | 176 |
1 files changed, 140 insertions, 36 deletions
diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx index db24ca0e..590fea0f 100644 --- a/src/components/rich_content/rich_content.jsx +++ b/src/components/rich_content/rich_content.jsx @@ -1,7 +1,7 @@ import Vue from 'vue' import { unescape, flattenDeep } from 'lodash' -import { convertHtml, getTagName, processTextForEmoji, getAttrs } from 'src/services/mini_html_converter/mini_html_converter.service.js' -import { processHtml } from 'src/services/tiny_post_html_processor/tiny_post_html_processor.service.js' +import { convertHtmlToTree, getTagName, processTextForEmoji, getAttrs } from 'src/services/html_converter/html_tree_converter.service.js' +import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js' import StillImage from 'src/components/still-image/still-image.vue' import MentionLink from 'src/components/mention_link/mention_link.vue' @@ -31,11 +31,24 @@ export default Vue.component('RichContent', { required: false, type: Boolean, default: false + }, + // Whether to hide last mentions (hellthreads) + hideLastMentions: { + required: false, + type: Boolean, + default: false + }, + // Whether to hide first mentions + hideFirstMentions: { + required: false, + type: Boolean, + default: false } }, render (h) { // Pre-process HTML - const html = this.greentext ? addGreentext(this.html) : this.html + const html = preProcessPerLine(this.html, this.greentext, this.hideLastMentions) + console.log(this.hideFirstMentions, this.hideLastMentions) const renderImage = (tag) => { return <StillImage @@ -45,18 +58,20 @@ export default Vue.component('RichContent', { } const renderMention = (attrs, children, encounteredText) => { - return <MentionLink - url={attrs.href} - content={flattenDeep(children).join('')} - firstMention={!encounteredText} - /> + return (this.hideFirstMentions && !encounteredText) + ? '' + : <MentionLink + url={attrs.href} + content={flattenDeep(children).join('')} + firstMention={!encounteredText} + /> } // We stop treating mentions as "first" ones when we encounter // non-whitespace text let encounteredText = false // Processor to use with mini_html_converter - const processItem = (item) => { + const processItem = (item, index, array, what) => { // Handle text nodes - just add emoji if (typeof item === 'string') { const emptyText = item.trim() === '' @@ -69,7 +84,7 @@ export default Vue.component('RichContent', { encounteredText = true } if (item.includes(':')) { - return processTextForEmoji( + unescapedItem = processTextForEmoji( unescapedItem, this.emoji, ({ shortcode, url }) => { @@ -81,9 +96,8 @@ export default Vue.component('RichContent', { /> } ) - } else { - return unescapedItem } + return unescapedItem } // Handle tag nodes @@ -98,6 +112,8 @@ export default Vue.component('RichContent', { const attrs = getAttrs(opener) if (attrs['class'] && attrs['class'].includes('mention')) { return renderMention(attrs, children, encounteredText) + } else if (attrs['class'] && attrs['class'].includes('hashtag')) { + return item // We'll handle it later } else { attrs.target = '_blank' return <a {...{ attrs }}> @@ -116,43 +132,129 @@ export default Vue.component('RichContent', { } } } + // Processor for back direction (for finding "last" stuff, just easier this way) + let encounteredTextReverse = false + const renderHashtag = (attrs, children, encounteredTextReverse) => { + attrs.target = '_blank' + if (!encounteredTextReverse) { + attrs['data-parser-last'] = true + } + return <a {...{ attrs }}> + { children.map(processItem) } + </a> + } + const processItemReverse = (item, index, array, what) => { + // Handle text nodes - just add emoji + if (typeof item === 'string') { + const emptyText = item.trim() === '' + if (emptyText) return encounteredTextReverse ? item : item.trim() + if (!encounteredTextReverse) encounteredTextReverse = true + return item + } else if (Array.isArray(item)) { + // Handle tag nodes + const [opener, children] = item + const Tag = getTagName(opener) + switch (Tag) { + case 'a': // replace mentions with MentionLink + if (!this.handleLinks) break + const attrs = getAttrs(opener) + // should only be this + if (attrs['class'] && attrs['class'].includes('hashtag')) { + return renderHashtag(attrs, children, encounteredTextReverse) + } + } + } + return item + } return <span class="RichContent"> { this.$slots.prefix } - { convertHtml(html).map(processItem) } + { convertHtmlToTree(html).map(processItem).reverse().map(processItemReverse).reverse() } { this.$slots.suffix } </span> } }) -export const addGreentext = (html) => { - try { - if (html.includes('>')) { - // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works - return processHtml(html, (string) => { - if ( - string.includes('>') && string - .replace(/<[^>]+?>/gi, '') // remove all tags - .replace(/@\w+/gi, '') // remove mentions (even failed ones) - .trim() - .startsWith('>') - ) { - return `<span class='greentext'>${string}</span>` +/** Pre-processing HTML + * + * Currently this does two things: + * - add green/cyantexting + * - wrap and mark last line containing only mentions as ".lastMentionsLine" for + * more compact hellthreads. + * + * @param {String} html - raw HTML to process + * @param {Boolean} greentext - whether to enable greentexting or not + * @param {Boolean} removeLastMentions - whether to remove last mentions + */ +export const preProcessPerLine = (html, greentext, removeLastMentions) => { + // Only mark first (last) encounter + let lastMentionsMarked = false + + return convertHtmlToLines(html).reverse().map((item, index, array) => { + if (!item.text) return item + const string = item.text + + // Greentext stuff + if (greentext && (string.includes('>') || string.includes('<'))) { + const cleanedString = string.replace(/<[^>]+?>/gi, '') // remove all tags + .replace(/@\w+/gi, '') // remove mentions (even failed ones) + .trim() + if (cleanedString.startsWith('>')) { + return `<span class='greentext'>${string}</span>` + } else if (cleanedString.startsWith('<')) { + return `<span class='cyantext'>${string}</span>` + } + } + + const tree = convertHtmlToTree(string) + + // If line has loose text, i.e. text outside a mention or a tag + // we won't touch mentions. + let hasLooseText = false + let hasMentions = false + const process = (item) => { + if (Array.isArray(item)) { + const [opener, children, closer] = item + const tag = getTagName(opener) + if (tag === 'a') { + const attrs = getAttrs(opener) + if (attrs['class'] && attrs['class'].includes('mention')) { + hasMentions = true + return [opener, children, closer] + } else { + hasLooseText = true + return [opener, children, closer] + } + } else if (tag === 'span' || tag === 'p') { + return [opener, [...children].reverse().map(process).reverse(), closer] } else { - return string + hasLooseText = true + return [opener, children, closer] + } + } + + if (typeof item === 'string') { + if (item.trim() !== '') { + hasLooseText = true } - }) + return item + } + } + + const result = [...tree].reverse().map(process).reverse() + + if (removeLastMentions && hasMentions && !hasLooseText && !lastMentionsMarked) { + lastMentionsMarked = true + return '' } else { - return html + return flattenDeep(result).join('') } - } catch (e) { - console.error('Failed to process status html', e) - return html - } + }).reverse().join('') } export const getHeadTailLinks = (html) => { // Exported object properties const firstMentions = [] // Mentions that appear in the beginning of post body + const lastMentions = [] // Mentions that appear at the end of post body const lastTags = [] // Tags that appear at the end of post body const writtenMentions = [] // All mentions that appear in post body const writtenTags = [] // All tags that appear in post body @@ -170,7 +272,7 @@ export const getHeadTailLinks = (html) => { } } - // Processor to use with mini_html_converter + // Processor to use with html_tree_converter const processItem = (item) => { // Handle text nodes - stop treating mentions as "first" when text encountered if (typeof item === 'string') { @@ -182,6 +284,7 @@ export const getHeadTailLinks = (html) => { } // Encountered text? That means tags we've been collectings aren't "last"! lastTags.splice(0) + lastMentions.splice(0) return } // Handle tag nodes @@ -197,6 +300,7 @@ export const getHeadTailLinks = (html) => { firstMentions.push(linkData) } writtenMentions.push(linkData) + lastMentions.push(linkData) } else if (attrs['class'].includes('hashtag')) { lastTags.push(linkData) writtenTags.push(linkData) @@ -206,6 +310,6 @@ export const getHeadTailLinks = (html) => { children && children.forEach(processItem) } } - convertHtml(html).forEach(processItem) - return { firstMentions, writtenMentions, writtenTags, lastTags } + convertHtmlToTree(html).forEach(processItem) + return { firstMentions, writtenMentions, writtenTags, lastTags, lastMentions } } |
