From e98a2af39e01e42a6dc24aa148eb67e98cb1dbef Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Wed, 18 Aug 2021 20:54:04 +0300
Subject: hopefully final fix for spacings

---
 src/components/rich_content/rich_content.jsx | 37 +++++++++++++++++-----------
 1 file changed, 23 insertions(+), 14 deletions(-)

(limited to 'src/components/rich_content/rich_content.jsx')

diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx
index 8ab007e3..d98947df 100644
--- a/src/components/rich_content/rich_content.jsx
+++ b/src/components/rich_content/rich_content.jsx
@@ -61,6 +61,8 @@ export default Vue.component('RichContent', {
     // Pre-process HTML
     const { newHtml: html } = preProcessPerLine(this.html, this.greentext)
     let currentMentions = null // Current chain of mentions, we group all mentions together
+    // This is used to recover spacing removed when parsing mentions
+    let lastSpacing = ''
 
     const lastTags = [] // Tags that appear at the end of post body
     const writtenMentions = [] // All mentions that appear in post body
@@ -119,14 +121,9 @@ export default Vue.component('RichContent', {
         if (emptyText) {
           // don't include spaces when processing mentions - we'll include them
           // in MentionsLine
+          lastSpacing = item
           return currentMentions !== null ? item.trim() : item
         }
-        // We add space with mentionsLine, otherwise non-text elements will
-        // stick to them.
-        if (currentMentions !== null) {
-          // single whitespace trim
-          item = item[0].match(/\s/) ? item.slice(1) : item
-        }
 
         currentMentions = null
         if (item.includes(':')) {
@@ -151,21 +148,32 @@ export default Vue.component('RichContent', {
         const [opener, children, closer] = item
         const Tag = getTagName(opener)
         const attrs = getAttrs(opener)
+        const previouslyMentions = currentMentions !== null
+        /* During grouping of mentions we trim all the empty text elements
+         * This padding is added to recover last space removed in case
+         * we have a tag right next to mentions
+         */
+        const mentionsLinePadding =
+              // Padding is only needed if we just finished parsing mentions
+              previouslyMentions &&
+              // Don't add padding if content is string and has padding already
+              !(children && typeof children[0] === 'string' && children[0].match(/^\s/))
+                ? lastSpacing
+                : ''
         switch (Tag) {
           case 'br':
             currentMentions = null
             break
           case 'img': // replace images with StillImage
-            return renderImage(opener)
+            return ['', [mentionsLinePadding, renderImage(opener)], '']
           case 'a': // replace mentions with MentionLink
             if (!this.handleLinks) break
             if (attrs['class'] && attrs['class'].includes('mention')) {
               // Handling mentions here
               return renderMention(attrs, children)
             } else {
-              // Everything else will be handled in reverse pass
               currentMentions = null
-              return item // We'll handle it later
+              break
             }
           case 'span':
             if (this.handleLinks && attrs['class'] && attrs['class'].includes('h-card')) {
@@ -174,9 +182,13 @@ export default Vue.component('RichContent', {
         }
 
         if (children !== undefined) {
-          return [opener, children.map(processItem), closer]
+          return [
+            opener,
+            [mentionsLinePadding].concat(children.map(processItem)),
+            closer
+          ]
         } else {
-          return item
+          return ['', [mentionsLinePadding, item], '']
         }
       }
     }
@@ -215,7 +227,6 @@ export default Vue.component('RichContent', {
 
         // Render tag as is
         if (children !== undefined) {
-          html.includes('freenode') && console.log('PASS2', children)
           const newChildren = Array.isArray(children)
             ? [...children].reverse().map(processItemReverse).reverse()
             : children
@@ -283,8 +294,6 @@ export const preProcessPerLine = (html, greentext) => {
 
   const lines = convertHtmlToLines(html)
   const newHtml = lines.reverse().map((item, index, array) => {
-    // Going over each line in reverse to detect last mentions,
-    // keeping non-text stuff as-is
     if (!item.text) return item
     const string = item.text
 
-- 
cgit v1.2.3-70-g09d2


From 39494439d359fa058079c9325efdff244a7e7890 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 23 Aug 2021 20:57:21 +0300
Subject: very minimalist hashtaglink implementation, also you can middle-click
 mentions now.

---
 src/components/hashtag_link/hashtag_link.js     | 36 +++++++++++++++++++++++++
 src/components/hashtag_link/hashtag_link.scss   |  6 +++++
 src/components/hashtag_link/hashtag_link.vue    | 19 +++++++++++++
 src/components/mention_link/mention_link.vue    |  5 ++--
 src/components/rich_content/rich_content.jsx    | 13 ++++-----
 test/unit/specs/components/rich_content.spec.js |  8 +++---
 6 files changed, 75 insertions(+), 12 deletions(-)
 create mode 100644 src/components/hashtag_link/hashtag_link.js
 create mode 100644 src/components/hashtag_link/hashtag_link.scss
 create mode 100644 src/components/hashtag_link/hashtag_link.vue

(limited to 'src/components/rich_content/rich_content.jsx')

diff --git a/src/components/hashtag_link/hashtag_link.js b/src/components/hashtag_link/hashtag_link.js
new file mode 100644
index 00000000..a2433c2a
--- /dev/null
+++ b/src/components/hashtag_link/hashtag_link.js
@@ -0,0 +1,36 @@
+import { extractTagFromUrl } from 'src/services/matcher/matcher.service.js'
+
+const HashtagLink = {
+  name: 'HashtagLink',
+  props: {
+    url: {
+      required: true,
+      type: String
+    },
+    content: {
+      required: true,
+      type: String
+    },
+    tag: {
+      required: false,
+      type: String,
+      default: ''
+    }
+  },
+  methods: {
+    onClick () {
+      const tag = this.tag || extractTagFromUrl(this.url)
+      if (tag) {
+        const link = this.generateTagLink(tag)
+        this.$router.push(link)
+      } else {
+        window.open(this.url, '_blank')
+      }
+    },
+    generateTagLink (tag) {
+      return `/tag/${tag}`
+    }
+  }
+}
+
+export default HashtagLink
diff --git a/src/components/hashtag_link/hashtag_link.scss b/src/components/hashtag_link/hashtag_link.scss
new file mode 100644
index 00000000..78e8fb99
--- /dev/null
+++ b/src/components/hashtag_link/hashtag_link.scss
@@ -0,0 +1,6 @@
+.HashtagLink {
+  position: relative;
+  white-space: normal;
+  display: inline-block;
+  color: var(--link);
+}
diff --git a/src/components/hashtag_link/hashtag_link.vue b/src/components/hashtag_link/hashtag_link.vue
new file mode 100644
index 00000000..918ed26b
--- /dev/null
+++ b/src/components/hashtag_link/hashtag_link.vue
@@ -0,0 +1,19 @@
+<template>
+  <span
+    class="HashtagLink"
+  >
+    <!-- eslint-disable vue/no-v-html -->
+    <a
+      :href="url"
+      class="original"
+      target="_blank"
+      @click.prevent="onClick"
+      v-html="content"
+    />
+    <!-- eslint-enable vue/no-v-html -->
+  </span>
+</template>
+
+<script src="./hashtag_link.js"/>
+
+<style lang="scss" src="./hashtag_link.scss"/>
diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue
index 625eb727..a22b486c 100644
--- a/src/components/mention_link/mention_link.vue
+++ b/src/components/mention_link/mention_link.vue
@@ -17,8 +17,9 @@
       :style="style"
       :class="classnames"
     >
-      <button
+      <a
         class="short button-unstyled"
+        :href="url"
         @click.prevent="onClick"
       >
         <!-- eslint-disable vue/no-v-html -->
@@ -35,7 +36,7 @@
           class="you"
         >{{ $t('status.you') }}</span>
         <!-- eslint-enable vue/no-v-html -->
-      </button>
+      </a>
       <span
         v-if="userName !== userNameFull"
         class="full popover-default"
diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx
index d98947df..f52b0370 100644
--- a/src/components/rich_content/rich_content.jsx
+++ b/src/components/rich_content/rich_content.jsx
@@ -5,6 +5,7 @@ import { convertHtmlToTree } from 'src/services/html_converter/html_tree_convert
 import { convertHtmlToLines } from 'src/services/html_converter/html_line_converter.service.js'
 import StillImage from 'src/components/still-image/still-image.vue'
 import MentionsLine, { MENTIONS_LIMIT } from 'src/components/mentions_line/mentions_line.vue'
+import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'
 
 import './rich_content.scss'
 
@@ -83,13 +84,10 @@ export default Vue.component('RichContent', {
     const renderHashtag = (attrs, children, encounteredTextReverse) => {
       const linkData = getLinkData(attrs, children, tagsIndex++)
       writtenTags.push(linkData)
-      attrs.target = '_blank'
       if (!encounteredTextReverse) {
         lastTags.push(linkData)
       }
-      return <a {...{ attrs }}>
-        { children.map(processItem) }
-      </a>
+      return <HashtagLink {...{ props: linkData }}/>
     }
 
     const renderMention = (attrs, children) => {
@@ -211,7 +209,10 @@ export default Vue.component('RichContent', {
             if (!this.handleLinks) break
             const attrs = getAttrs(opener)
             // should only be this
-            if (attrs['class'] && attrs['class'].includes('hashtag')) {
+            if (
+              (attrs['class'] && attrs['class'].includes('hashtag')) || // Pleroma style
+                (attrs['rel'] === 'tag') // Mastodon style
+            ) {
               return renderHashtag(attrs, children, encounteredTextReverse)
             } else {
               attrs.target = '_blank'
@@ -275,7 +276,7 @@ const getLinkData = (attrs, children, index) => {
   return {
     index,
     url: attrs.href,
-    hashtag: attrs['data-tag'],
+    tag: attrs['data-tag'],
     content: flattenDeep(children).join(''),
     textContent
   }
diff --git a/test/unit/specs/components/rich_content.spec.js b/test/unit/specs/components/rich_content.spec.js
index b29edeab..f6c478a9 100644
--- a/test/unit/specs/components/rich_content.spec.js
+++ b/test/unit/specs/components/rich_content.spec.js
@@ -300,10 +300,10 @@ describe('RichContent', () => {
       '<p>',
       '<a href="http://macrochan.org/images/N/H/NHCMDUXJPPZ6M3Z2CQ6D2EBRSWGE7MZY.jpg" target="_blank">',
       'NHCMDUXJPPZ6M3Z2CQ6D2EBRSWGE7MZY.jpg</a>',
-      ' <a class="hashtag" data-tag="nou" href="https://shitposter.club/tag/nou" target="_blank">',
-      '#nou</a>',
-      ' <a class="hashtag" data-tag="screencap" href="https://shitposter.club/tag/screencap" target="_blank">',
-      '#screencap</a>',
+      ' <hashtaglink-stub url="https://shitposter.club/tag/nou" content="#nou" tag="nou">',
+      '</hashtaglink-stub>',
+      ' <hashtaglink-stub url="https://shitposter.club/tag/screencap" content="#screencap" tag="screencap">',
+      '</hashtaglink-stub>',
       ' </p>'
     ].join('')
 
-- 
cgit v1.2.3-70-g09d2


From 4d73eaa6cef7a8b81bcca719755b11c05717d395 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Mon, 23 Aug 2021 21:36:18 +0300
Subject: fix spacing before hashtags

---
 src/components/rich_content/rich_content.jsx | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

(limited to 'src/components/rich_content/rich_content.jsx')

diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx
index f52b0370..c0d20c5e 100644
--- a/src/components/rich_content/rich_content.jsx
+++ b/src/components/rich_content/rich_content.jsx
@@ -181,9 +181,12 @@ export default Vue.component('RichContent', {
 
         if (children !== undefined) {
           return [
-            opener,
-            [mentionsLinePadding].concat(children.map(processItem)),
-            closer
+            '',
+            [
+              mentionsLinePadding,
+              [opener, children.map(processItem), closer]
+            ],
+            ''
           ]
         } else {
           return ['', [mentionsLinePadding, item], '']
-- 
cgit v1.2.3-70-g09d2

