refactor: ♻️ use linkify and dompurify to show good descriptions
All checks were successful
repod / xml (push) Successful in 19s
repod / php (push) Successful in 1m1s
repod / nodejs (push) Successful in 1m25s
repod / release (push) Has been skipped

This commit is contained in:
Michel Roux 2024-08-08 11:37:48 +02:00
parent e63ff6ef04
commit e190a9eeb6
6 changed files with 54 additions and 21 deletions

10
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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>

View 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>

View File

@ -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() {

View File

@ -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>')
}