aboutsummaryrefslogtreecommitdiff
path: root/src/components/rich_content
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/rich_content')
-rw-r--r--src/components/rich_content/rich_content.jsx176
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('&gt;')) {
- // This checks if post has '>' at the beginning, excluding mentions so that @mention >impying works
- return processHtml(html, (string) => {
- if (
- string.includes('&gt;') && string
- .replace(/<[^>]+?>/gi, '') // remove all tags
- .replace(/@\w+/gi, '') // remove mentions (even failed ones)
- .trim()
- .startsWith('&gt;')
- ) {
- 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('&gt;') || string.includes('&lt;'))) {
+ const cleanedString = string.replace(/<[^>]+?>/gi, '') // remove all tags
+ .replace(/@\w+/gi, '') // remove mentions (even failed ones)
+ .trim()
+ if (cleanedString.startsWith('&gt;')) {
+ return `<span class='greentext'>${string}</span>`
+ } else if (cleanedString.startsWith('&lt;')) {
+ 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 }
}