diff options
| author | Till Glöggler <till@gundk.it> | 2026-02-27 16:55:27 +0100 |
|---|---|---|
| committer | Till Glöggler <till@gundk.it> | 2026-02-27 16:55:27 +0100 |
| commit | c6413ed6a55f4c18bc938d29dca700db0f52acbf (patch) | |
| tree | 7ca3d8aee245e10c75a5ecde08786c5e1c7b176c | |
| parent | ce679651ccf784da2e4bf57d53b57d895a4fbea3 (diff) | |
fixes #3252, correctly escape and display filenames containing htmlbiest-3252
| -rw-r--r-- | package-lock.json | 35 | ||||
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | resources/vue/base-components.js | 1 | ||||
| -rw-r--r-- | resources/vue/components/FilesTable.vue | 27 | ||||
| -rw-r--r-- | resources/vue/components/StudipHighlightText.vue | 32 |
5 files changed, 48 insertions, 48 deletions
diff --git a/package-lock.json b/package-lock.json index 09d74d5..e672221 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,7 @@ "@fullcalendar/resource": "^6.1.19", "@fullcalendar/vue3": "^6.1.19", "@vojtechlanka/vue-tags-input": "^3.1.1", - "jsonapi-serializer": "^3.6.9", - "qrcode.vue": "^3.6.0", - "sqids": "^0.3.0" + "jsonapi-serializer": "^3.6.9" }, "devDependencies": { "@axe-core/playwright": "^4.6.1", @@ -95,12 +93,13 @@ "postcss": "^8.4.49", "postcss-loader": "^8.1.1", "postcss-scss": "^4.0.4", + "qrcode.vue": "^3.6.0", "raw-loader": "^4.0.2", - "sanitize-html": "^2.7.0", "sass": "^1.29.0", "sass-loader": "^16.0.4", "select2": "4.0.13", "sprintf-js": "^1.0.3", + "sqids": "^0.3.0", "stream-browserify": "^3.0.0", "style-loader": "^4.0.0", "stylelint": "^15.11.0", @@ -10800,11 +10799,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/parse-srcset": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, "node_modules/parse5": { "version": "7.1.2", "dev": true, @@ -11601,6 +11595,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/qrcode.vue/-/qrcode.vue-3.6.0.tgz", "integrity": "sha512-vQcl2fyHYHMjDO1GguCldJxepq2izQjBkDEEu9NENgfVKP6mv/e2SU62WbqYHGwTgWXLhxZ1NCD1dAZKHQq1fg==", + "dev": true, "license": "MIT", "peerDependencies": { "vue": "^3.0.0" @@ -12092,27 +12087,6 @@ "dev": true, "license": "MIT" }, - "node_modules/sanitize-html": { - "version": "2.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "deepmerge": "^4.2.2", - "escape-string-regexp": "^4.0.0", - "htmlparser2": "^8.0.0", - "is-plain-object": "^5.0.0", - "parse-srcset": "^1.0.2", - "postcss": "^8.3.11" - } - }, - "node_modules/sanitize-html/node_modules/is-plain-object": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sass": { "version": "1.69.0", "dev": true, @@ -12535,6 +12509,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/sqids/-/sqids-0.3.0.tgz", "integrity": "sha512-lOQK1ucVg+W6n3FhRwwSeUijxe93b51Bfz5PMRMihVf1iVkl82ePQG7V5vwrhzB11v0NtsR25PSZRGiSomJaJw==", + "dev": true, "license": "MIT" }, "node_modules/stack-utils": { diff --git a/package.json b/package.json index 0d48bad..8278e7e 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,6 @@ "postcss-scss": "^4.0.4", "qrcode.vue": "^3.6.0", "raw-loader": "^4.0.2", - "sanitize-html": "^2.7.0", "sass": "^1.29.0", "sass-loader": "^16.0.4", "select2": "4.0.13", diff --git a/resources/vue/base-components.js b/resources/vue/base-components.js index dd6561e..71dcbfe 100644 --- a/resources/vue/base-components.js +++ b/resources/vue/base-components.js @@ -26,6 +26,7 @@ const BaseComponents = { StudipDialog: defineAsyncComponent(() => import('./components/StudipDialog.vue')), StudipFileSize: defineAsyncComponent(() => import('./components/StudipFileSize.vue')), StudipFolderSize: defineAsyncComponent(() => import('./components/StudipFolderSize.vue')), + StudipHighlightText: defineAsyncComponent(() => import('./components/StudipHighlightText.vue')), StudipIcon: defineAsyncComponent(() => import('./components/StudipIcon.vue')), StudipMessageBox: defineAsyncComponent(() => import('./components/StudipMessageBox.vue')), StudipMultiPersonSearch: defineAsyncComponent(() => import('./components/StudipMultiPersonSearch.vue')), diff --git a/resources/vue/components/FilesTable.vue b/resources/vue/components/FilesTable.vue index 2bc83ce..2f9a5ad 100644 --- a/resources/vue/components/FilesTable.vue +++ b/resources/vue/components/FilesTable.vue @@ -177,7 +177,7 @@ <a :href="folder.url" :title="$gettext('Ordner %{foldername} öffnen', { foldername: folder.name}, true)" > - <span v-html="highlightString(folder.name)"></span> + <studip-highlight-text :text="folder.name" :search-term="needleForFilter"></studip-highlight-text> </a> </td> <td class="responsive-hidden" :data-sort-value="folder.object_count"> @@ -189,9 +189,11 @@ </td> <td class="responsive-hidden" :class="{'filter-match': valueMatchesFilter(folder.author_name)}"> <a v-if="folder.author_url" :href="folder.author_url"> - <span v-html="highlightString(folder.author_name)"></span> + <studip-highlight-text :text="folder.author_name" :search-term="needleForFilter"></studip-highlight-text> </a> - <span v-else v-html="highlightString(folder.author_name)"></span> + <template v-else> + <studip-highlight-text :text="folder.author_name" :search-term="needleForFilter"></studip-highlight-text> + </template> </td> <td class="responsive-hidden" style="white-space: nowrap;"> <studip-date-time :timestamp="folder.chdate" :relative="true"></studip-date-time> @@ -245,7 +247,7 @@ :id="`file-${file.id}`" :title="$gettext('Details zur Datei %{filename} anzeigen', { filename: file.name }, true)" > - <span v-html="highlightString(file.name)"></span> + <studip-highlight-text :text="file.name" :search-term="needleForFilter"></studip-highlight-text> <studip-icon v-if="file.isAccessible" shape="accessibility" role="info" @@ -270,9 +272,11 @@ </td> <td class="responsive-hidden" :class="{'filter-match': valueMatchesFilter(file.author_name)}"> <a v-if="file.author_url" :href="file.author_url"> - <span v-html="highlightString(file.author_name)"></span> + <studip-highlight-text :text="file.author_name" :search-term="needleForFilter"></studip-highlight-text> </a> - <span v-else v-html="highlightString(file.author_name)"></span> + <template v-else> + <studip-highlight-text :text="file.author_name" :search-term="needleForFilter"></studip-highlight-text> + </template> </td> <td data-sort-value="file.chdate" class="responsive-hidden" style="white-space: nowrap;"> <studip-date-time :timestamp="file.chdate" :relative="true"></studip-date-time> @@ -312,7 +316,6 @@ </div> </template> <script> -import sanitizeHTML from 'sanitize-html'; export default { name: 'files-table', @@ -447,16 +450,6 @@ export default { } return string.toLowerCase().includes(this.needleForFilter); }, - highlightString (string) { - let highlighted = sanitizeHTML(string); - if (this.needleForFilter.length > 0) { - // Escape needle for regexp, see https://stackoverflow.com/a/3561711 - const pattern = this.needleForFilter.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&') - const regExp = new RegExp(pattern, 'gi'); - highlighted = highlighted.replace(regExp, '<span class="filter-match">$&</span>'); - } - return highlighted; - }, getAriaLabelForFolder(folder) { return this.$gettext( 'Ordner %{name} auswählen', diff --git a/resources/vue/components/StudipHighlightText.vue b/resources/vue/components/StudipHighlightText.vue new file mode 100644 index 0000000..8ac73f4 --- /dev/null +++ b/resources/vue/components/StudipHighlightText.vue @@ -0,0 +1,32 @@ +<template> + <span> + <template v-for="(part, index) in computedParts" :key="index"> + {{ part }}<span v-if="searchTerm && index < computedParts.length - 1" class="filter-match">{{ searchTerm }}</span> + </template> + </span> +</template> + +<script> +export default { + props: { + text: { + type: String, + default: '' + }, + searchTerm: { + type: String, + default: '' + } + }, + computed: { + computedParts() { + if (!this.searchTerm) { + return [this.text || '']; + } + + console.log(this.text, (this.text || '').split(this.searchTerm)); + return (this.text || '').split(this.searchTerm); + } + } +}; +</script> |
