summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLouis Chemineau <louis@chmn.me>2023-01-10 14:45:38 +0100
committerLouis Chemineau <louis@chmn.me>2023-01-10 14:45:38 +0100
commit76389fce86ae5b2beb9b6a1b8dbd0c3cd1628b73 (patch)
tree359161f20abc86e049f07e3d8c7ef0f3eb3c1085 /src
parentd67db0c4e04195b3d8f2536beb76fe35b6b5ee96 (diff)
Revert removal of MessageContent
Fix https://github.com/nextcloud/social/issues/1561 Signed-off-by: Louis Chemineau <louis@chmn.me>
Diffstat (limited to 'src')
-rw-r--r--src/components/MessageContent.js263
-rw-r--r--src/components/TimelinePost.vue16
2 files changed, 271 insertions, 8 deletions
diff --git a/src/components/MessageContent.js b/src/components/MessageContent.js
new file mode 100644
index 00000000..c297c8d3
--- /dev/null
+++ b/src/components/MessageContent.js
@@ -0,0 +1,263 @@
+import Vue from 'vue'
+import Emoji from './Emoji.vue'
+
+export default Vue.component('MessageContent', {
+ props: {
+ source: {
+ type: Object,
+ required: true,
+ },
+ },
+ render(createElement) {
+ return formatMessage(createElement, this.source)
+ },
+})
+
+/**
+ * Transform the message source into Vue elements
+ *
+ * filters out all tags except <br />, <p>, <span> and <a>.
+ *
+ * Links that are hashtags or mentions are rewritten to link to the local profile or hashtag page
+ * All external links have `rel="nofollow noopener noreferrer"` and `target="_blank"` set.
+ *
+ * All attributes other than `href` for links are stripped from the source
+ *
+ * @param createElement
+ * @param source
+ */
+export function formatMessage(createElement, source) {
+ if (!source.tag) {
+ source.tag = []
+ }
+ const mentions = source.tag.filter(tag => tag.type === 'Mention')
+ const hashtags = source.tag.filter(tag => tag.type === 'Hashtag')
+
+ const parser = new DOMParser()
+ const dom = parser.parseFromString(`<div id="rootwrapper">${source.content}</div>`, 'text/html')
+ const element = dom.getElementById('rootwrapper')
+ const cleaned = cleanCopy(createElement, element, { mentions, hashtags })
+ return cleaned
+}
+
+/**
+ *
+ * @param createElement
+ * @param node
+ * @param context
+ */
+function domToVue(createElement, node, context) {
+ switch (node.tagName) {
+ case 'P':
+ return cleanCopy(createElement, node, context)
+ case 'BR':
+ return cleanCopy(createElement, node, context)
+ case 'SPAN':
+ return cleanCopy(createElement, node, context)
+ case 'A':
+ return cleanLink(createElement, node, context)
+ default:
+ return transformText(createElement, node.textContent)
+ }
+}
+
+const mentionRegex = /(\W|^)((@\w+)@[\w.-_]+)/i
+const hashTagRegex = /(\W|^)(#\w+)/i
+
+/**
+ *
+ * @param createElement
+ * @param text
+ */
+function transformText(createElement, text) {
+ return transformTextRegex(text, [
+ {
+ regex: mentionRegex,
+ onMatch: match => [
+ match[1],
+ createElement(
+ 'router-link',
+ {
+ props: {
+ to: {
+ name: 'profile',
+ params: { account: match[2].slice(1) },
+ },
+ },
+ },
+ [match[3]]
+ ),
+ ],
+ },
+ {
+ regex: hashTagRegex,
+ onMatch: match => [
+ match[1],
+ createElement(
+ 'router-link',
+ {
+ props: {
+ to: {
+ name: 'tags',
+ params: { tag: match[2].slice(1) },
+ },
+ },
+ },
+ [match[2]]
+ ),
+ ],
+ },
+ {
+ regex: emojiRe,
+ onMatch: match => createElement(
+ Emoji,
+ {
+ props: {
+ emoji: match[0],
+ },
+ }
+ ),
+ },
+ ])
+}
+
+/**
+ * copy a node without any attributes and cleaning all children
+ *
+ * @param createElement
+ * @param node
+ * @param context
+ */
+function cleanCopy(createElement, node, context) {
+ const children = Array.from(node.childNodes).map(node => domToVue(createElement, node, context))
+ return createElement(node.tagName, children)
+}
+
+/**
+ *
+ * @param createElement
+ * @param node
+ * @param context
+ */
+function cleanLink(createElement, node, context) {
+ const type = getLinkType(node.className)
+ const attributes = {}
+
+ switch (type) {
+ case 'mention':
+ var tag = matchMention(context.mentions, node.getAttribute('href'), node.textContent)
+ if (tag) {
+ attributes.rel = 'nofollow noopener noreferrer'
+ attributes.target = '_blank'
+ attributes.href = node.getAttribute('href')
+ attributes.title = tag.name
+
+ return createElement('a', { attrs: attributes }, [transformText(createElement, node.textContent)])
+ } else {
+ return transformText(createElement, node.textContent)
+ }
+ case 'hashtag':
+ return createElement(
+ 'router-link',
+ {
+ props: {
+ to: {
+ name: 'tags',
+ params: { tag: node.textContent.slice(1) },
+ },
+ },
+ },
+ [node.textContent]
+ )
+ default:
+ attributes.rel = 'nofollow noopener noreferrer'
+ attributes.target = '_blank'
+ attributes.href = node.getAttribute('href')
+
+ return createElement('a', { attrs: attributes }, [transformText(createElement, node.textContent)])
+ }
+}
+
+/**
+ *
+ * @param className
+ */
+function getLinkType(className) {
+ const parts = className.split(' ')
+ if (parts.includes('hashtag')) {
+ return 'hashtag'
+ }
+ if (parts.includes('mention')) {
+ return 'mention'
+ }
+ return ''
+}
+
+/**
+ *
+ * @param tags
+ * @param mentionHref
+ * @param mentionText
+ */
+function matchMention(tags, mentionHref, mentionText) {
+ const mentionUrl = new URL(mentionHref)
+ for (const tag of tags) {
+ if (mentionText === tag.name) {
+ return tag
+ }
+
+ // since the mention link href is not always equal to the href in the tag
+ // we instead match the server and username separate
+ const tagUrl = new URL(tag.href)
+ if (tagUrl.host === mentionUrl.host) {
+ const [, name] = tag.name.split('@')
+ if (name === mentionText || '@' + name === mentionText) {
+ return tag
+ }
+ }
+ }
+ return null
+}
+
+// RegExp based on emoji's official Unicode standards
+// http://www.unicode.org/Public/UNIDATA/EmojiSources.txt
+const emojiRe = /(?:\ud83d\udc68\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc68\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc68\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc68\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc68\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffc-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffd-\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb\udffc\udffe\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffd\udfff]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc68\ud83c[\udffb-\udffe]|\ud83d\udc69\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83d\udc69\ud83c[\udffb-\udffe]|\ud83e\uddd1\ud83c\udffb\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffc\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffd\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udffe\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\ud83c\udfff\u200d\ud83e\udd1d\u200d\ud83e\uddd1\ud83c[\udffb-\udfff]|\ud83e\uddd1\u200d\ud83e\udd1d\u200d\ud83e\uddd1|\ud83d\udc6b\ud83c[\udffb-\udfff]|\ud83d\udc6c\ud83c[\udffb-\udfff]|\ud83d\udc6d\ud83c[\udffb-\udfff]|\ud83d[\udc6b-\udc6d])|(?:\ud83d[\udc68\udc69]|\ud83e\uddd1)(?:\ud83c[\udffb-\udfff])?\u200d(?:\u2695\ufe0f|\u2696\ufe0f|\u2708\ufe0f|\ud83c[\udf3e\udf73\udf7c\udf84\udf93\udfa4\udfa8\udfeb\udfed]|\ud83d[\udcbb\udcbc\udd27\udd2c\ude80\ude92]|\ud83e[\uddaf-\uddb3\uddbc\uddbd])|(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75]|\u26f9)((?:\ud83c[\udffb-\udfff]|\ufe0f)\u200d[\u2640\u2642]\ufe0f)|(?:\ud83c[\udfc3\udfc4\udfca]|\ud83d[\udc6e\udc70\udc71\udc73\udc77\udc81\udc82\udc86\udc87\ude45-\ude47\ude4b\ude4d\ude4e\udea3\udeb4-\udeb6]|\ud83e[\udd26\udd35\udd37-\udd39\udd3d\udd3e\uddb8\uddb9\uddcd-\uddcf\uddd6-\udddd])(?:\ud83c[\udffb-\udfff])?\u200d[\u2640\u2642]\ufe0f|(?:\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d\udc68|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d\udc8b\u200d\ud83d[\udc68\udc69]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\u2764\ufe0f\u200d\ud83d\udc68|\ud83d\udc68\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc68\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\u2764\ufe0f\u200d\ud83d[\udc68\udc69]|\ud83d\udc69\u200d\ud83d\udc66\u200d\ud83d\udc66|\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83c\udff3\ufe0f\u200d\u26a7\ufe0f|\ud83c\udff3\ufe0f\u200d\ud83c\udf08|\ud83c\udff4\u200d\u2620\ufe0f|\ud83d\udc15\u200d\ud83e\uddba|\ud83d\udc3b\u200d\u2744\ufe0f|\ud83d\udc41\u200d\ud83d\udde8|\ud83d\udc68\u200d\ud83d[\udc66\udc67]|\ud83d\udc69\u200d\ud83d[\udc66\udc67]|\ud83d\udc6f\u200d\u2640\ufe0f|\ud83d\udc6f\u200d\u2642\ufe0f|\ud83e\udd3c\u200d\u2640\ufe0f|\ud83e\udd3c\u200d\u2642\ufe0f|\ud83e\uddde\u200d\u2640\ufe0f|\ud83e\uddde\u200d\u2642\ufe0f|\ud83e\udddf\u200d\u2640\ufe0f|\ud83e\udddf\u200d\u2642\ufe0f|\ud83d\udc08\u200d\u2b1b)|[#*0-9]\ufe0f?\u20e3|(?:[©®\u2122\u265f]\ufe0f)|(?:\ud83c[\udc04\udd70\udd71\udd7e\udd7f\ude02\ude1a\ude2f\ude37\udf21\udf24-\udf2c\udf36\udf7d\udf96\udf97\udf99-\udf9b\udf9e\udf9f\udfcd\udfce\udfd4-\udfdf\udff3\udff5\udff7]|\ud83d[\udc3f\udc41\udcfd\udd49\udd4a\udd6f\udd70\udd73\udd76-\udd79\udd87\udd8a-\udd8d\udda5\udda8\uddb1\uddb2\uddbc\uddc2-\uddc4\uddd1-\uddd3\udddc-\uddde\udde1\udde3\udde8\uddef\uddf3\uddfa\udecb\udecd-\udecf\udee0-\udee5\udee9\udef0\udef3]|[\u203c\u2049\u2139\u2194-\u2199\u21a9\u21aa\u231a\u231b\u2328\u23cf\u23ed-\u23ef\u23f1\u23f2\u23f8-\u23fa\u24c2\u25aa\u25ab\u25b6\u25c0\u25fb-\u25fe\u2600-\u2604\u260e\u2611\u2614\u2615\u2618\u2620\u2622\u2623\u2626\u262a\u262e\u262f\u2638-\u263a\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267b\u267f\u2692-\u2697\u2699\u269b\u269c\u26a0\u26a1\u26a7\u26aa\u26ab\u26b0\u26b1\u26bd\u26be\u26c4\u26c5\u26c8\u26cf\u26d1\u26d3\u26d4\u26e9\u26ea\u26f0-\u26f5\u26f8\u26fa\u26fd\u2702\u2708\u2709\u270f\u2712\u2714\u2716\u271d\u2721\u2733\u2734\u2744\u2747\u2757\u2763\u2764\u27a1\u2934\u2935\u2b05-\u2b07\u2b1b\u2b1c\u2b50\u2b55\u3030\u303d\u3297\u3299])(?:\ufe0f|(?!\ufe0e))|(?:(?:\ud83c[\udfcb\udfcc]|\ud83d[\udd74\udd75\udd90]|[\u261d\u26f7\u26f9\u270c\u270d])(?:\ufe0f|(?!\ufe0e))|(?:\ud83c[\udf85\udfc2-\udfc4\udfc7\udfca]|\ud83d[\udc42\udc43\udc46-\udc50\udc66-\udc69\udc6e\udc70-\udc78\udc7c\udc81-\udc83\udc85-\udc87\udcaa\udd7a\udd95\udd96\ude45-\ude47\ude4b-\ude4f\udea3\udeb4-\udeb6\udec0\udecc]|\ud83e[\udd0c\udd0f\udd18-\udd1c\udd1e\udd1f\udd26\udd30-\udd39\udd3d\udd3e\udd77\uddb5\uddb6\uddb8\uddb9\uddbb\uddcd-\uddcf\uddd1-\udddd]|[\u270a\u270b]))(?:\ud83c[\udffb-\udfff])?|(?:\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc65\udb40\udc6e\udb40\udc67\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc73\udb40\udc63\udb40\udc74\udb40\udc7f|\ud83c\udff4\udb40\udc67\udb40\udc62\udb40\udc77\udb40\udc6c\udb40\udc73\udb40\udc7f|\ud83c\udde6\ud83c[\udde8-\uddec\uddee\uddf1\uddf2\uddf4\uddf6-\uddfa\uddfc\uddfd\uddff]|\ud83c\udde7\ud83c[\udde6\udde7\udde9-\uddef\uddf1-\uddf4\uddf6-\uddf9\uddfb\uddfc\uddfe\uddff]|\ud83c\udde8\ud83c[\udde6\udde8\udde9\uddeb-\uddee\uddf0-\uddf5\uddf7\uddfa-\uddff]|\ud83c\udde9\ud83c[\uddea\uddec\uddef\uddf0\uddf2\uddf4\uddff]|\ud83c\uddea\ud83c[\udde6\udde8\uddea\uddec\udded\uddf7-\uddfa]|\ud83c\uddeb\ud83c[\uddee-\uddf0\uddf2\uddf4\uddf7]|\ud83c\uddec\ud83c[\udde6\udde7\udde9-\uddee\uddf1-\uddf3\uddf5-\uddfa\uddfc\uddfe]|\ud83c\udded\ud83c[\uddf0\uddf2\uddf3\uddf7\uddf9\uddfa]|\ud83c\uddee\ud83c[\udde8-\uddea\uddf1-\uddf4\uddf6-\uddf9]|\ud83c\uddef\ud83c[\uddea\uddf2\uddf4\uddf5]|\ud83c\uddf0\ud83c[\uddea\uddec-\uddee\uddf2\uddf3\uddf5\uddf7\uddfc\uddfe\uddff]|\ud83c\uddf1\ud83c[\udde6-\udde8\uddee\uddf0\uddf7-\uddfb\uddfe]|\ud83c\uddf2\ud83c[\udde6\udde8-\udded\uddf0-\uddff]|\ud83c\uddf3\ud83c[\udde6\udde8\uddea-\uddec\uddee\uddf1\uddf4\uddf5\uddf7\uddfa\uddff]|\ud83c\uddf4\ud83c\uddf2|\ud83c\uddf5\ud83c[\udde6\uddea-\udded\uddf0-\uddf3\uddf7-\uddf9\uddfc\uddfe]|\ud83c\uddf6\ud83c\udde6|\ud83c\uddf7\ud83c[\uddea\uddf4\uddf8\uddfa\uddfc]|\ud83c\uddf8\ud83c[\udde6-\uddea\uddec-\uddf4\uddf7-\uddf9\uddfb\uddfd-\uddff]|\ud83c\uddf9\ud83c[\udde6\udde8\udde9\uddeb-\udded\uddef-\uddf4\uddf7\uddf9\uddfb\uddfc\uddff]|\ud83c\uddfa\ud83c[\udde6\uddec\uddf2\uddf3\uddf8\uddfe\uddff]|\ud83c\uddfb\ud83c[\udde6\udde8\uddea\uddec\uddee\uddf3\uddfa]|\ud83c\uddfc\ud83c[\uddeb\uddf8]|\ud83c\uddfd\ud83c\uddf0|\ud83c\uddfe\ud83c[\uddea\uddf9]|\ud83c\uddff\ud83c[\udde6\uddf2\uddfc]|\ud83c[\udccf\udd8e\udd91-\udd9a\udde6-\uddff\ude01\ude32-\ude36\ude38-\ude3a\ude50\ude51\udf00-\udf20\udf2d-\udf35\udf37-\udf7c\udf7e-\udf84\udf86-\udf93\udfa0-\udfc1\udfc5\udfc6\udfc8\udfc9\udfcf-\udfd3\udfe0-\udff0\udff4\udff8-\udfff]|\ud83d[\udc00-\udc3e\udc40\udc44\udc45\udc51-\udc65\udc6a\udc6f\udc79-\udc7b\udc7d-\udc80\udc84\udc88-\udca9\udcab-\udcfc\udcff-\udd3d\udd4b-\udd4e\udd50-\udd67\udda4\uddfb-\ude44\ude48-\ude4a\ude80-\udea2\udea4-\udeb3\udeb7-\udebf\udec1-\udec5\uded0-\uded2\uded5-\uded7\udeeb\udeec\udef4-\udefc\udfe0-\udfeb]|\ud83e[\udd0d\udd0e\udd10-\udd17\udd1d\udd20-\udd25\udd27-\udd2f\udd3a\udd3c\udd3f-\udd45\udd47-\udd76\udd78\udd7a-\uddb4\uddb7\uddba\uddbc-\uddcb\uddd0\uddde-\uddff\ude70-\ude74\ude78-\ude7a\ude80-\ude86\ude90-\udea8\udeb0-\udeb6\udec0-\udec2\uded0-\uded6]|[\u23e9-\u23ec\u23f0\u23f3\u267e\u26ce\u2705\u2728\u274c\u274e\u2753-\u2755\u2795-\u2797\u27b0\u27bf\ue50a])|\ufe0f/
+
+/**
+ *
+ * @param text
+ * @param handlers
+ */
+function transformTextRegex(text, handlers) {
+ const parts = []
+
+ while (text.length > 0) {
+ const result = handlers.reduce((bestMatch, handler) => {
+ let match
+ if ((match = handler.regex.exec(text))) {
+ if (bestMatch.index === -1 || match.index < bestMatch.index) {
+ return {
+ index: match.index,
+ match,
+ onMatch: handler.onMatch,
+ }
+ }
+ }
+ return bestMatch
+ }, { index: -1 })
+
+ if (result.index !== -1) {
+ if (result.index > 0) {
+ parts.push(text.slice(0, result.index))
+ }
+
+ parts.push(result.onMatch(result.match))
+ text = text.slice(result.index + result.match[0].length)
+ } else {
+ parts.push(text)
+ return parts
+ }
+ }
+
+ return parts
+}
diff --git a/src/components/TimelinePost.vue b/src/components/TimelinePost.vue
index 0d97f21f..ccbd06fb 100644
--- a/src/components/TimelinePost.vue
+++ b/src/components/TimelinePost.vue
@@ -25,10 +25,7 @@
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-if="item.content" class="post-message">
- <RichText :text="source"
- :autolink="true"
- :reference-limit="2"
- :arguments="richParameters" />
+ <MessageContent :source="source" />
</div>
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-else class="post-message" v-html="item.actor_info.summary" />
@@ -95,6 +92,7 @@ import logger from '../services/logger.js'
import moment from '@nextcloud/moment'
import { generateUrl } from '@nextcloud/router'
import RichText from '@nextcloud/vue-richtext'
+import MessageContent from './MessageContent.js'
export default {
name: 'TimelinePost',
@@ -108,6 +106,7 @@ export default {
Heart,
HeartOutline,
RichText,
+ MessageContent,
},
mixins: [currentUser],
props: {
@@ -124,11 +123,12 @@ export default {
source() {
if (!this.item.source && this.item.content) {
// local posts don't have a source json
- console.debug(this.item.content)
- return this.item.content
+ return {
+ content: this.item.content,
+ tag: [],
+ }
}
- console.debug(JSON.parse(this.item.source))
- return JSON.parse(this.item.source).content
+ return JSON.parse(this.item.source)
},
avatarUrl() {
return generateUrl('/apps/social/api/v1/global/actor/avatar?id=' + this.item.attributedTo)