Skip to content

Commit 86c7e70

Browse files
authored
Merge pull request #484 from nextmcloud/nmc/5829-search-favorite-status-is-not-reflected-in-search-result-preview
nmc/5829-search-favorite-status-is-not-reflected-in-search-result-preview
2 parents 928bbc6 + c2e979c commit 86c7e70

4 files changed

Lines changed: 129 additions & 0 deletions

File tree

css/components/search.scss

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,9 @@
262262
}
263263

264264
&-item__icon {
265+
position: relative;
266+
overflow: visible;
267+
265268
&.icon-folder,
266269
&--no-preview {
267270
width: 42px;
@@ -271,6 +274,19 @@
271274
background-repeat: no-repeat;
272275
background-position: center;
273276
}
277+
278+
.nmc-search-favorite {
279+
position: absolute;
280+
top: 0;
281+
right: -6px;
282+
width: 18px;
283+
height: 18px;
284+
background-image: var(--icon-starred-yellow);
285+
background-size: contain;
286+
background-repeat: no-repeat;
287+
background-position: center;
288+
display: block;
289+
}
274290
}
275291

276292
&-footer {

lib/Listener/BeforeTemplateRenderedListener.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ public function handle(Event $event): void {
9797
\OCP\Util::addScript('nmctheme', 'nmctheme-nmcfiles', "core");
9898
\OCP\Util::addScript("nmctheme", "nmctheme-mimetypes", "core");
9999
\OCP\Util::addScript("nmctheme", "nmctheme-skipactions", "core");
100+
\OCP\Util::addScript("nmctheme", "nmctheme-searchfavorites", "core");
100101
\OCP\Util::addScript("nmctheme", "nmctheme-filessettings", "files");
101102
\OCP\Util::addScript("nmctheme", "nmctheme-filelistplugin", "files");
102103
}

src/js/searchfavorites.js

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/**
2+
* @copyright Copyright (c) 2024 T-Systems International
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-or-later
5+
*
6+
* Shows a favorite star indicator on favorited files/folders in unified search results.
7+
*/
8+
9+
const LOG = '[nmc-search-favorites]'
10+
11+
let favoriteIdsPromise = null
12+
13+
function getRequestToken() {
14+
return document.head.dataset.requesttoken
15+
?? window.OC?.requestToken
16+
?? ''
17+
}
18+
19+
function getUserId() {
20+
return window.OC?.currentUser ?? ''
21+
}
22+
23+
/**
24+
* Fetches all favorited file IDs for the current user in a single REPORT request.
25+
* Result is cached for the page lifetime.
26+
*/
27+
async function loadFavoriteIds() {
28+
if (favoriteIdsPromise) return favoriteIdsPromise
29+
30+
favoriteIdsPromise = (async () => {
31+
const userId = getUserId()
32+
if (!userId) return new Set()
33+
34+
const url = `/remote.php/dav/files/${encodeURIComponent(userId)}/`
35+
36+
try {
37+
const response = await fetch(url, {
38+
method: 'REPORT',
39+
headers: {
40+
'Content-Type': 'application/xml; charset=utf-8',
41+
requesttoken: getRequestToken(),
42+
},
43+
body: '<?xml version="1.0"?>'
44+
+ '<oc:filter-files xmlns:d="DAV:" xmlns:oc="http://owncloud.org/ns">'
45+
+ '<d:prop><oc:fileid/><oc:favorite/></d:prop>'
46+
+ '<oc:filter-rules><oc:favorite>1</oc:favorite></oc:filter-rules>'
47+
+ '</oc:filter-files>',
48+
credentials: 'same-origin',
49+
})
50+
51+
if (!response.ok) {
52+
console.warn(LOG, `REPORT failed: HTTP ${response.status}`)
53+
return new Set()
54+
}
55+
56+
const text = await response.text()
57+
const doc = new DOMParser().parseFromString(text, 'application/xml')
58+
const ids = new Set()
59+
60+
Array.from(doc.getElementsByTagNameNS('http://owncloud.org/ns', 'fileid'))
61+
.forEach(el => ids.add(el.textContent.trim()))
62+
63+
return ids
64+
} catch (err) {
65+
console.error(LOG, 'Error loading favorites:', err)
66+
return new Set()
67+
}
68+
})()
69+
70+
return favoriteIdsPromise
71+
}
72+
73+
function addFavoriteMarker(item) {
74+
const iconEl = item.querySelector('.result-item__icon')
75+
if (!iconEl || iconEl.querySelector('.nmc-search-favorite')) return
76+
77+
const marker = document.createElement('span')
78+
marker.className = 'nmc-search-favorite'
79+
marker.setAttribute('aria-hidden', 'true')
80+
iconEl.appendChild(marker)
81+
}
82+
83+
async function processResultItem(item, favoriteIds) {
84+
item.dataset.nmcFavChecked = '1'
85+
86+
const link = item.querySelector('a[href*="/index.php/f/"]')
87+
if (!link) return
88+
89+
const match = link.href.match(/\/index\.php\/f\/(\d+)/)
90+
if (!match) return
91+
92+
const fileId = match[1]
93+
if (favoriteIds.has(fileId)) {
94+
addFavoriteMarker(item)
95+
}
96+
}
97+
98+
async function processUnprocessedItems() {
99+
const unprocessed = [
100+
...document.querySelectorAll('.result-item:not([data-nmc-fav-checked])'),
101+
]
102+
if (unprocessed.length === 0) return
103+
104+
const favoriteIds = await loadFavoriteIds()
105+
unprocessed.forEach(item => processResultItem(item, favoriteIds))
106+
}
107+
108+
window.addEventListener('DOMContentLoaded', () => {
109+
const observer = new MutationObserver(processUnprocessedItems)
110+
observer.observe(document.body, { childList: true, subtree: true })
111+
})

webpack.config.cjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ webpackConfig.entry = {
99
filessettings: path.join(__dirname, 'src', 'js', 'filessettings.js'),
1010
filelistplugin: path.join(__dirname, 'src', 'js', 'filelistplugin.js'),
1111
skipactions: path.join(__dirname, 'src', 'js', 'skipactions.js'),
12+
searchfavorites: path.join(__dirname, 'src', 'js', 'searchfavorites.js'),
1213
conflictdialog: path.join(__dirname, 'src', 'js', 'conflictdialog.js'),
1314
mimetypes: path.join(__dirname, 'src', 'js', 'mimetypes.js'),
1415
nmcfooter: path.join(__dirname, 'src', 'nmcfooter.ts'),

0 commit comments

Comments
 (0)