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/l10n": "^3.1.0",
|
||||||
"@nextcloud/router": "^3.0.1",
|
"@nextcloud/router": "^3.0.1",
|
||||||
"@nextcloud/vue": "^8.16.0",
|
"@nextcloud/vue": "^8.16.0",
|
||||||
|
"dompurify": "^3.1.6",
|
||||||
|
"linkify-html": "^4.1.3",
|
||||||
"vue": "^2",
|
"vue": "^2",
|
||||||
"vue-material-design-icons": "^5.3.0",
|
"vue-material-design-icons": "^5.3.0",
|
||||||
"vue-router": "^3",
|
"vue-router": "^3",
|
||||||
@ -7876,6 +7878,14 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"peer": 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": {
|
"node_modules/linkify-it": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
"@nextcloud/l10n": "^3.1.0",
|
"@nextcloud/l10n": "^3.1.0",
|
||||||
"@nextcloud/router": "^3.0.1",
|
"@nextcloud/router": "^3.0.1",
|
||||||
"@nextcloud/vue": "^8.16.0",
|
"@nextcloud/vue": "^8.16.0",
|
||||||
|
"dompurify": "^3.1.6",
|
||||||
|
"linkify-html": "^4.1.3",
|
||||||
"vue": "^2",
|
"vue": "^2",
|
||||||
"vue-material-design-icons": "^5.3.0",
|
"vue-material-design-icons": "^5.3.0",
|
||||||
"vue-router": "^3",
|
"vue-router": "^3",
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<!-- eslint-disable vue/no-v-html -->
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<NcAvatar :display-name="name" :is-no-user="true" :size="256" :url="image" />
|
<NcAvatar :display-name="name" :is-no-user="true" :size="256" :url="image" />
|
||||||
<h2>{{ name }}</h2>
|
<h2>{{ name }}</h2>
|
||||||
<p v-html="strippedDescription" />
|
<SafeHtml :source="description" />
|
||||||
<div>
|
<div>
|
||||||
<NcButton v-if="link" :href="link" target="_blank">
|
<NcButton v-if="link" :href="link" target="_blank">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
@ -25,7 +24,7 @@
|
|||||||
import { NcAvatar, NcButton } from '@nextcloud/vue'
|
import { NcAvatar, NcButton } from '@nextcloud/vue'
|
||||||
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
import DownloadIcon from 'vue-material-design-icons/Download.vue'
|
||||||
import OpenInNewIcon from 'vue-material-design-icons/OpenInNew.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'
|
import { humanFileSize } from '../../utils/size.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -35,6 +34,7 @@ export default {
|
|||||||
NcAvatar,
|
NcAvatar,
|
||||||
NcButton,
|
NcButton,
|
||||||
OpenInNewIcon,
|
OpenInNewIcon,
|
||||||
|
SafeHtml,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
description: {
|
description: {
|
||||||
@ -70,9 +70,6 @@ export default {
|
|||||||
episodeFileSize() {
|
episodeFileSize() {
|
||||||
return humanFileSize(this.size)
|
return humanFileSize(this.size)
|
||||||
},
|
},
|
||||||
strippedDescription() {
|
|
||||||
return cleanHtml(this.description)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</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>
|
<template>
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<img class="background" :src="imageUrl" />
|
<img class="background" :src="imageUrl" />
|
||||||
@ -21,9 +20,7 @@
|
|||||||
<i>{{ author }}</i>
|
<i>{{ author }}</i>
|
||||||
</a>
|
</a>
|
||||||
<br /><br />
|
<br /><br />
|
||||||
<p>
|
<SafeHtml :source="description" />
|
||||||
<small v-html="strippedDescription" />
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<NcAppNavigationNew
|
<NcAppNavigationNew
|
||||||
v-if="!isSubscribed"
|
v-if="!isSubscribed"
|
||||||
@ -43,8 +40,8 @@ import { NcAppNavigationNew, NcAvatar } from '@nextcloud/vue'
|
|||||||
import { showError, showSuccess } from '@nextcloud/dialogs'
|
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||||
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
import PlusIcon from 'vue-material-design-icons/Plus.vue'
|
||||||
import RssIcon from 'vue-material-design-icons/Rss.vue'
|
import RssIcon from 'vue-material-design-icons/Rss.vue'
|
||||||
|
import SafeHtml from '../Atoms/SafeHtml.vue'
|
||||||
import axios from '@nextcloud/axios'
|
import axios from '@nextcloud/axios'
|
||||||
import { cleanHtml } from '../../utils/text.js'
|
|
||||||
import { decodeUrl } from '../../utils/url.js'
|
import { decodeUrl } from '../../utils/url.js'
|
||||||
import { generateUrl } from '@nextcloud/router'
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
|
||||||
@ -55,6 +52,7 @@ export default {
|
|||||||
NcAppNavigationNew,
|
NcAppNavigationNew,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
RssIcon,
|
RssIcon,
|
||||||
|
SafeHtml,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
author: {
|
author: {
|
||||||
@ -85,9 +83,6 @@ export default {
|
|||||||
isSubscribed() {
|
isSubscribed() {
|
||||||
return this.$store.state.subscriptions.subscriptions.includes(this.url)
|
return this.$store.state.subscriptions.subscriptions.includes(this.url)
|
||||||
},
|
},
|
||||||
strippedDescription() {
|
|
||||||
return cleanHtml(this.description)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async addSubscription() {
|
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