|
| 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 | +}) |
0 commit comments