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() })