diff options
| author | Henry Jameson <me@hjkos.com> | 2021-06-10 18:52:01 +0300 |
|---|---|---|
| committer | Henry Jameson <me@hjkos.com> | 2021-06-10 18:52:01 +0300 |
| commit | cc00af7a3102034b05ebcd4aa1fd01c6f467184a (patch) | |
| tree | fc2d34177416a03359a85567aa6b8c29374c2f07 /src/components | |
| parent | 0f73e96194fb13e70be0222a7ab718d7894b62c2 (diff) | |
Hellthread(tm) Certified
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/mention_link/mention_link.js | 3 | ||||
| -rw-r--r-- | src/components/mention_link/mention_link.vue | 1 | ||||
| -rw-r--r-- | src/components/rich_content/rich_content.jsx | 176 | ||||
| -rw-r--r-- | src/components/status/status.js | 3 | ||||
| -rw-r--r-- | src/components/status/status.vue | 1 | ||||
| -rw-r--r-- | src/components/status_body/status_body.js | 8 | ||||
| -rw-r--r-- | src/components/status_body/status_body.vue | 12 | ||||
| -rw-r--r-- | src/components/status_content/status_content.js | 1 | ||||
| -rw-r--r-- | src/components/status_content/status_content.vue | 3 |
9 files changed, 163 insertions, 45 deletions
diff --git a/src/components/mention_link/mention_link.js b/src/components/mention_link/mention_link.js index 711c87d6..00b9e388 100644 --- a/src/components/mention_link/mention_link.js +++ b/src/components/mention_link/mention_link.js @@ -70,9 +70,6 @@ const MentionLink = { highlightClass () { if (this.highlight) return highlightClass(this.user) }, - oldPlace () { - return !this.mergedConfig.mentionsOwnLine - }, oldStyle () { return !this.mergedConfig.mentionsNewStyle }, diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue index 281fab25..a65dbad3 100644 --- a/src/components/mention_link/mention_link.vue +++ b/src/components/mention_link/mention_link.vue @@ -1,7 +1,6 @@ <template> <span class="MentionLink" - :class="{ '-oldPlace': oldPlace }" > <!-- eslint-disable vue/no-v-html --> <a 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 } } diff --git a/src/components/status/status.js b/src/components/status/status.js index e9a5ec0d..bab818fc 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -196,6 +196,9 @@ const Status = { hasMentionsLine () { return this.mentionsLine.length > 0 }, + hideLastMentions () { + return this.headTailLinks.firstMentions.length === 0 + }, muted () { if (this.statusoid.user.id === this.currentUser.id) return false const { status } = this diff --git a/src/components/status/status.vue b/src/components/status/status.vue index 7cc25be9..0190d864 100644 --- a/src/components/status/status.vue +++ b/src/components/status/status.vue @@ -306,6 +306,7 @@ :highlight="highlight" :focused="isFocused" :hide-first-mentions="mentionsOwnLine && isReply" + :hide-last-mentions="hideLastMentions" :head-tail-links="headTailLinks" @mediaplay="addMediaPlaying($event)" @mediapause="removeMediaPlaying($event)" diff --git a/src/components/status_body/status_body.js b/src/components/status_body/status_body.js index c2edb601..2fc9abbf 100644 --- a/src/components/status_body/status_body.js +++ b/src/components/status_body/status_body.js @@ -30,7 +30,8 @@ const StatusContent = { // if this was computed at upper level it can be passed here, otherwise // it will be in this component 'headTailLinks', - 'hideFirstMentions' + 'hideFirstMentions', + 'hideLastMentions' ], data () { return { @@ -80,9 +81,12 @@ const StatusContent = { attachmentTypes () { return this.status.attachments.map(file => fileType.fileType(file.mimetype)) }, - mentions () { + mentionsFirst () { return this.headTailLinksComputed.firstMentions }, + mentionsLast () { + return this.headTailLinksComputed.lastMentions + }, ...mapGetters(['mergedConfig']) }, components: { diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue index 4df29934..bd599a8c 100644 --- a/src/components/status_body/status_body.vue +++ b/src/components/status_body/status_body.vue @@ -49,11 +49,19 @@ :emoji="status.emojis" :handle-links="true" :greentext="mergedConfig.greentext" + :hide-first-mentions="hideFirstMentions" + :hide-last-mentions="hideLastMentions" > <template v-slot:prefix> <MentionsLine - v-if="!hideFirstMentions" - :mentions="mentions" + v-if="!hideFirstMentions && mentionsFirst" + :mentions="mentionsFirst" + /> + </template> + <template v-slot:suffix> + <MentionsLine + v-if="!hideFirstMentions && mentionsLast" + :mentions="mentionsLast" /> </template> </RichContent> diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js index 363a9cb0..64cc6d44 100644 --- a/src/components/status_content/status_content.js +++ b/src/components/status_content/status_content.js @@ -33,6 +33,7 @@ const StatusContent = { 'fullContent', 'singleLine', 'hideFirstMentions', + 'hideLastMentions', 'headTailLinks' ], computed: { diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue index 18f6e7be..c32bbbfb 100644 --- a/src/components/status_content/status_content.vue +++ b/src/components/status_content/status_content.vue @@ -5,7 +5,8 @@ :status="status" :single-line="singleLine" :hide-first-mentions="hideFirstMentions" - :headTailLinks="headTailLinks" + :hide-last-mentions="hideLastMentions" + :head-tail-links="headTailLinks" > <div v-if="status.poll && status.poll.options"> <poll :base-poll="status.poll" /> |
