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 /resources | |
| parent | ce679651ccf784da2e4bf57d53b57d895a4fbea3 (diff) | |
fixes #3252, correctly escape and display filenames containing htmlbiest-3252
Diffstat (limited to 'resources')
| -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 |
3 files changed, 43 insertions, 17 deletions
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> |
