refactor: ♻️ use linkify and dompurify to show good descriptions
This commit is contained in:
parent
e63ff6ef04
commit
e190a9eeb6
10
package-lock.json
generated
10
package-lock.json
generated
@ -13,6 +13,8 @@
|
||||
"@nextcloud/l10n": "^3.1.0",
|
||||
"@nextcloud/router": "^3.0.1",
|
||||
"@nextcloud/vue": "^8.16.0",
|
||||
"dompurify": "^3.1.6",
|
||||
"linkify-html": "^4.1.3",
|
||||
"vue": "^2",
|
||||
"vue-material-design-icons": "^5.3.0",
|
||||
"vue-router": "^3",
|
||||
@ -7876,6 +7878,14 @@
|
||||
"dev": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/linkify-html": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/linkify-html/-/linkify-html-4.1.3.tgz",
|
||||
"integrity": "sha512-Ejb8X/pOxB4IVqG1U37tnF85UW3JtX+eHudH3zlZ2pODz2e/J7zQ/vj+VDWffwhTecJqdRehhluwrRmKoJz+iQ==",
|
||||
"peerDependencies": {
|
||||
"linkifyjs": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/linkify-it": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||
|
@ -19,6 +19,8 @@
|
||||
"@nextcloud/l10n": "^3.1.0",
|
||||
"@nextcloud/router": "^3.0.1",
|
||||
"@nextcloud/vue": "^8.16.0",
|
||||
"dompurify": "^3.1.6",
|
||||
"linkify-html": "^4.1.3",
|
||||
"vue": "^2",
|
||||
"vue-material-design-icons": "^5.3.0",
|
||||
"vue-router": "^3",
|
||||
|
@ -1,9 +1,8 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div>
|
||||
<NcAvatar :display-name="name" :is-no-user="true" :size="256" :url="image" />
|
||||
<h2>{{ name }}</h2>
|
||||
<p v-html="strippedDescription" />
|
||||
<SafeHtml :source="description" />
|
||||
<div>
|
||||
<NcButton v-if="link" :href="link" target="_blank">
|
||||
<template #icon>
|
||||
@ -25,7 +24,7 @@
|
||||
import { NcAvatar, NcButton } from '@nextcloud/vue'
|
||||
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
||||
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.vue'
|
||||
import { cleanHtml } from '../../utils/text.js'
|
||||
import SafeHtml from './SafeHtml.vue'
|
||||
import { humanFileSize } from '../../utils/size.js'
|
||||
|
||||
export default {
|
||||
@ -35,6 +34,7 @@ export default {
|
||||
NcAvatar,
|
||||
NcButton,
|
||||
OpenInNewIcon,
|
||||
SafeHtml,
|
||||
},
|
||||
props: {
|
||||
description: {
|
||||
@ -70,9 +70,6 @@ export default {
|
||||
episodeFileSize() {
|
||||
return humanFileSize(this.size)
|
||||
},
|
||||
strippedDescription() {
|
||||
return cleanHtml(this.description)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
36
src/components/Atoms/SafeHtml.vue
Normal file
36
src/components/Atoms/SafeHtml.vue
Normal file
@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div v-sanitize="source" class="html" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import linkifyHtml from 'linkify-html'
|
||||
import { sanitize } from 'dompurify'
|
||||
|
||||
export default {
|
||||
name: 'SafeHtml',
|
||||
directives: {
|
||||
sanitize: {
|
||||
inserted(el, binding) {
|
||||
el.innerHTML = sanitize(
|
||||
linkifyHtml(binding.value, {
|
||||
nl2br: true,
|
||||
target: '_blank',
|
||||
}),
|
||||
)
|
||||
},
|
||||
},
|
||||
},
|
||||
props: {
|
||||
source: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.html a {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
@ -1,4 +1,3 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="header">
|
||||
<img class="background" :src="imageUrl" />
|
||||
@ -21,9 +20,7 @@
|
||||
<i>{{ author }}</i>
|
||||
</a>
|
||||
<br /><br />
|
||||
<p>
|
||||
<small v-html="strippedDescription" />
|
||||
</p>
|
||||
<SafeHtml :source="description" />
|
||||
</div>
|
||||
<NcAppNavigationNew
|
||||
v-if="!isSubscribed"
|
||||
@ -43,8 +40,8 @@ import { NcAppNavigationNew, NcAvatar } from '@nextcloud/vue'
|
||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
||||
import RssIcon from 'vue-material-design-icons/Rss.vue'
|
||||
import SafeHtml from '../Atoms/SafeHtml.vue'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { cleanHtml } from '../../utils/text.js'
|
||||
import { decodeUrl } from '../../utils/url.js'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
|
||||
@ -55,6 +52,7 @@ export default {
|
||||
NcAppNavigationNew,
|
||||
PlusIcon,
|
||||
RssIcon,
|
||||
SafeHtml,
|
||||
},
|
||||
props: {
|
||||
author: {
|
||||
@ -85,9 +83,6 @@ export default {
|
||||
isSubscribed() {
|
||||
return this.$store.state.subscriptions.subscriptions.includes(this.url)
|
||||
},
|
||||
strippedDescription() {
|
||||
return cleanHtml(this.description)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async addSubscription() {
|
||||
|
@ -1,7 +0,0 @@
|
||||
// https://stackoverflow.com/a/5002618
|
||||
export const cleanHtml = (text) => {
|
||||
const pre = document.createElement('pre')
|
||||
pre.innerHTML = text.replace(/<br\s*\/?>/gm, '\n')
|
||||
const strippedText = pre.textContent || pre.innerText || ''
|
||||
return strippedText.replace(/\n/gm, '<br>')
|
||||
}
|
Loading…
Reference in New Issue
Block a user