From 501f0228973b8f2a6f1dbe8af2aca7686b302d4e Mon Sep 17 00:00:00 2001
From: Mikhail Sazanov <m@sazanof.ru>
Date: Tue, 21 Jun 2022 13:35:45 +0300
Subject: [PATCH] Draft quotes builder

Signed-off-by: Mikhail Sazanov <m@sazanof.ru>
---
 src/components/HtmlBlockQuote.vue     | 143 ++++++++++++++++++++++++++
 src/components/HtmlBlockQuoteItem.vue | 135 ++++++++++++++++++++++++
 src/html-body-quotes.js               |  32 ++++++
 src/html-response.js                  |   4 +
 4 files changed, 314 insertions(+)
 create mode 100644 src/components/HtmlBlockQuote.vue
 create mode 100644 src/components/HtmlBlockQuoteItem.vue
 create mode 100644 src/html-body-quotes.js

diff --git a/src/components/HtmlBlockQuote.vue b/src/components/HtmlBlockQuote.vue
new file mode 100644
index 0000000000..8069473ecd
--- /dev/null
+++ b/src/components/HtmlBlockQuote.vue
@@ -0,0 +1,143 @@
+<template>
+	<div class="quotes">
+		<a class="btn" @click="showAllQuotes()">
+			<CommentQuote :size="18" />
+			Toggle {{ total }} quotes
+		</a>
+		<HtmlBlockQuoteItem :quote="quotes" @toggleQuote="toggleQuote(quotes)" />
+	</div>
+</template>
+
+<script>
+
+import HtmlBlockQuoteItem from './HtmlBlockQuoteItem'
+import CommentQuote from 'vue-material-design-icons/CommentQuote'
+
+const REGEXP_EMAIL = /([!#-'*+/-9=?A-Z^-~-]+(\.[!#-'*+/-9=?A-Z^-~-]+)*|(\[\]!#-[^-~ \t]|(\\[\t -~]))+")@([!#-'*+/-9=?A-Z^-~-]+(\.[!#-'*+/-9=?A-Z^-~-]+)*|\[[\t -Z^-~]*])/
+const REGEXP_LABEL = /"(.*)"/
+
+export default {
+	name: 'HtmlBlockQuote',
+	components: {
+		HtmlBlockQuoteItem,
+		CommentQuote,
+	},
+	props: {
+		quote: {
+			type: HTMLElement,
+			required: true,
+		},
+		blockquote: {
+			type: HTMLElement,
+			required: true,
+		},
+	},
+	data() {
+		return {
+			quotes: {},
+			total: null,
+		}
+	},
+	mounted() {
+		this.quotes = this.findAndToggleBlockquotesRecursive(this.quote, this.blockquote)
+	},
+	methods: {
+		findAndToggleBlockquotesRecursive(quote, blockquote) {
+			if (quote === null && blockquote !== null) {
+				quote = blockquote
+			}
+
+			if (quote !== null && blockquote !== null) {
+				const _quote = this.getQuoteHeaderData(quote)
+
+				if (blockquote.nodeName === 'BLOCKQUOTE') {
+					const subBlockquote = blockquote.querySelector('blockquote')
+					const subQuote = subBlockquote !== null ? subBlockquote.previousElementSibling : null
+					if (subBlockquote !== null) {
+						subBlockquote.remove()
+						if (subQuote !== null) {
+							 subQuote.remove()
+						}
+					}
+
+					this.total++
+
+					return {
+						id: this.quotes.length,
+						text: _quote.text,
+						email: _quote.email,
+						label: _quote.label,
+						date: _quote.date,
+						hide: true,
+						blockquoteText: blockquote.innerHTML,
+						childs: this.findAndToggleBlockquotesRecursive(subQuote, subBlockquote),
+					}
+				}
+			}
+		},
+
+		getQuoteHeaderData(quote) {
+			const text = quote.innerText.trim()
+			const email = REGEXP_EMAIL.exec(text)
+			const label = REGEXP_LABEL.exec(text)
+			const date = text.split(' – ')
+
+			const data = {
+				text: null,
+				label: label !== null ? label[1] : text,
+				email: email !== null ? email[0] : null,
+				date: date !== null && date.length > 1 ? date[1].trim() : null,
+			}
+			data.text = data.email === null && data.label === null ? null : text
+			return data
+		},
+
+		showAllQuotes(quote) {
+			const hide = this.quotes.hide
+			const _quote = !quote ? this.quotes : quote
+			if (typeof (_quote.childs) !== 'undefined') {
+				this.showAllQuotes(_quote.childs)
+			}
+			_quote.hide = !hide
+		},
+
+		toggleQuote(quote) {
+			quote.hide = !quote.hide
+		},
+	},
+}
+</script>
+
+<style lang="scss" scoped>
+
+	a.btn {
+		display:inline-flex;
+		margin: 20px 0 20px -26px;
+		color:#666;
+		text-decoration: none;
+		cursor: pointer;
+
+		& > span {
+			margin-right: 5px;
+		}
+	}
+
+	.show-all-quotes {
+		display:inline-flex;
+		align-items: center;
+		margin-bottom: 5px;
+		padding: 4px;
+		background: #ddd;
+		border-radius: 20px;
+		cursor: pointer;
+	}
+
+	.quotes {
+		margin-top: 16px;
+		margin-left: 50px;
+
+		.quote {
+			margin-left: 0;
+		}
+	}
+</style>
diff --git a/src/components/HtmlBlockQuoteItem.vue b/src/components/HtmlBlockQuoteItem.vue
new file mode 100644
index 0000000000..83ae079d89
--- /dev/null
+++ b/src/components/HtmlBlockQuoteItem.vue
@@ -0,0 +1,135 @@
+<template>
+	<div class="quote" :class="{ 'last': !quote.childs }">
+		<div class="quote-header" :class="{ 'active': !quote.hide }" @click="$emit('toggleQuote')">
+			<Avatar v-if="quote.label"
+				:url="avatarUrl"
+				:email="quote.email"
+				:display-name="quote.label"
+				:disable-tooltip="true"
+				:disable-menu="true"
+				:size="24" />
+			<FormatQuoteClose v-else-if="quote.hide" :size="24" class="quoterdiv" />
+			<FormatQuoteOpen v-else :size="24" class="quoterdiv" />
+			<div v-if="!quote.label">
+				{{ quote.text }}
+			</div>
+			<div v-else class="quote-header-text">
+				<span>{{ quote.label }}
+					<a :href="`mailto:${quote.email}`">{{ quote.email }}</a>
+					<span v-if="quote.date" class="quote-date">{{ quote.date }}</span>
+				</span>
+			</div>
+		</div>
+		<div v-if="!quote.hide" class="quote-body">
+			<div class="quote-content" v-html="quote.blockquoteText" />
+			<HtmlBlockQuoteItem v-if="quote.childs" :quote="quote.childs" @toggleQuote="toggleQuote(quote.childs)" />
+		</div>
+	</div>
+</template>
+
+<script>
+// import { fetchAvatarUrlMemoized } from '../service/AvatarService'
+import Avatar from '@nextcloud/vue/dist/Components/Avatar'
+import FormatQuoteClose from 'vue-material-design-icons/FormatQuoteClose'
+import FormatQuoteOpen from 'vue-material-design-icons/FormatQuoteOpen'
+
+export default {
+	name: 'HtmlBlockQuoteItem',
+	components: {
+		Avatar,
+		FormatQuoteClose,
+		FormatQuoteOpen,
+	},
+	props: {
+		quote: {
+			type: Object,
+			required: true,
+		},
+	},
+	data() {
+		return {
+			hide: this.quote.hide,
+			avatarUrl: undefined,
+		}
+	},
+	async mounted() {
+		if (this.quote.email !== '') {
+			// this.avatarUrl = await fetchAvatarUrlMemoized(this.quote.email)
+		}
+	},
+	methods: {
+		toggleQuote(quote) {
+			quote.hide = !quote.hide
+		},
+	},
+}
+</script>
+
+<style lang="scss" scoped>
+	.quote {
+		margin-bottom: 10px;
+
+		.quote-header {
+			display: inline-flex;
+			flex-wrap: wrap;
+			align-items: center;
+			cursor: pointer;
+			transition: 0.3s;
+			padding: 3px 16px 3px 3px;
+			margin-left: -34px;
+
+			.avatardiv {
+				border-radius: 50%;
+				color: #fff;
+				margin-right: 6px;
+				width: 24px;
+			}
+
+			.quoterdiv {
+				border-radius: 50%;
+				color: #333;
+				margin-right: 6px;
+			}
+
+			.quote-header-text {
+				width: calc(100% - 30px)
+			}
+
+			&:hover, &.active {
+				background: #efefef;
+				border-radius: 20px;
+			}
+
+			.quote-date {
+				opacity: 0.5;
+			}
+		}
+
+		.quote-body {
+			padding: 10px 0;
+
+			.quote-content {
+				position: relative;
+				color: #666;
+				display: inline-block;
+
+				&:before {
+					content: "";
+					position: absolute;
+					width: 24px;
+					top: -13px;
+					bottom: -13px;
+					left: -20px;
+					border-left: 2px solid #efefef;
+					z-index: -1;
+				}
+			}
+		}
+		&.last {
+			.quote-content:before {
+				display: none
+			}
+		}
+	}
+
+</style>
diff --git a/src/html-body-quotes.js b/src/html-body-quotes.js
new file mode 100644
index 0000000000..e6636de6b2
--- /dev/null
+++ b/src/html-body-quotes.js
@@ -0,0 +1,32 @@
+import Vue from 'vue'
+import Nextcloud from './mixins/Nextcloud'
+import HtmlBlockQuote from './components/HtmlBlockQuote'
+
+const vueOfBlockQuote = () => {
+	// very hardcode logic
+	const blockquote = document.querySelector('blockquote')
+	if (blockquote !== null) {
+		const quote = blockquote.previousElementSibling
+		// remove first quote in body, if exists
+		quote.remove()
+
+		if (quote === null) {
+			return false
+		}
+		Vue.mixin(Nextcloud)
+		return new Vue({
+			el: document.querySelector('blockquote'),
+			render: (h) => h(HtmlBlockQuote, {
+				props: {
+					quote,
+					blockquote,
+				},
+			}),
+		})
+	}
+	return false
+}
+
+export {
+	vueOfBlockQuote,
+}
diff --git a/src/html-response.js b/src/html-response.js
index 1b9459101e..62ee0b5111 100644
--- a/src/html-response.js
+++ b/src/html-response.js
@@ -25,6 +25,8 @@ import '../css/html-response.css'
 // iframe-resizer client script
 import 'iframe-resizer/js/iframeResizer.contentWindow.js'
 
+import { vueOfBlockQuote } from './html-body-quotes.js'
+
 // Fix width of some newsletter mails
 document.addEventListener('DOMContentLoaded', function() {
 	for (const el of document.querySelectorAll('*')) {
@@ -32,4 +34,6 @@ document.addEventListener('DOMContentLoaded', function() {
 			el.style['max-width'] = '100%'
 		}
 	}
+
+	vueOfBlockQuote()
 })