aboutsummaryrefslogtreecommitdiff
path: root/resources
diff options
context:
space:
mode:
authorTill Glöggler <till@gundk.it>2026-02-27 16:55:27 +0100
committerTill Glöggler <till@gundk.it>2026-02-27 16:55:27 +0100
commitc6413ed6a55f4c18bc938d29dca700db0f52acbf (patch)
tree7ca3d8aee245e10c75a5ecde08786c5e1c7b176c /resources
parentce679651ccf784da2e4bf57d53b57d895a4fbea3 (diff)
fixes #3252, correctly escape and display filenames containing htmlbiest-3252
Diffstat (limited to 'resources')
-rw-r--r--resources/vue/base-components.js1
-rw-r--r--resources/vue/components/FilesTable.vue27
-rw-r--r--resources/vue/components/StudipHighlightText.vue32
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>