|
26 | 26 | if ( |
27 | 27 | key === "fileCount" || |
28 | 28 | key === "addBannerDimension" || |
29 | | - key === "performerProfileCards" |
| 29 | + key === "performerProfileCards" || |
| 30 | + key === "stashIDIcon" |
30 | 31 | ) { |
31 | 32 | acc[key] = settings[key]; |
32 | 33 | } else { |
|
42 | 43 | ".performer-card:hover img.performer-card-image{box-shadow: 0 0 0 rgb(0 0 0 / 20%), 0 0 6px rgb(0 0 0 / 90%);transition: box-shadow .5s .5s}@media (min-width: 1691px){.performer-recommendations .card .performer-card-image{height: unset}}button.btn.favorite-button.not-favorite,button.btn.favorite-button.favorite{transition: filter .5s .5s}.performer-card:hover .thumbnail-section button.btn.favorite-button.not-favorite, .performer-card:hover .thumbnail-section button.btn.favorite-button.favorite{filter: drop-shadow(0 0 2px rgba(0, 0, 0, .9))}.performer-card .thumbnail-section button.btn.favorite-button.not-favorite, .performer-card .thumbnail-section button.btn.favorite-button.favorite{top: 10px;filter: drop-shadow(0 2px 2px rgba(0, 0, 0, .9))}.item-list-container .performer-card__age,.recommendation-row .performer-card__age,.item-list-container .performer-card .card-section-title,.recommendation-row .performer-card .card-section-title,.item-list-container .performer-card .thumbnail-section,.recommendation-row .performer-card .thumbnail-section{display: flex;align-content: center;justify-content: center}.item-list-container .performer-card .thumbnail-section a,.recommendation-row .performer-card .thumbnail-section a{display: contents}.item-list-container .performer-card-image,.recommendation-row .performer-card-image{aspect-ratio: 1 / 1;display: flex;object-fit: cover;border: 3px solid var(--plex-yelow);border-radius: 50%;min-width: unset;position: relative;width: 58%;margin: auto;z-index: 1;margin-top: 1.5rem;box-shadow:0 13px 26px rgb(0 0 0 / 20%),0 3px 6px rgb(0 0 0 / 90%);object-position: center;transition: box-shadow .5s .5s}.item-list-container .performer-card hr,.recommendation-row .performer-card hr{width: 90%}.item-list-container .performer-card .fi,.recommendation-row .performer-card .fi{position: absolute;top: 81.5%;left: 69%;border-radius: 50% !important;background-size: cover;margin-left: -1px;height: 1.5rem;width: 1.5rem;z-index: 10;border: solid 2px #252525;box-shadow: unset}.item-list-container .performer-card .card-popovers .btn,.recommendation-row .performer-card .card-popovers .btn{font-size: 0.9rem}"; |
43 | 44 | const RATING_BANNER_3D_STYLE = |
44 | 45 | ".grid-card{overflow:unset}.detail-group .rating-banner-3d,.rating-banner{display:none}.grid-card:hover .rating-banner-3d{opacity:0;transition:opacity .5s}.rating-banner-3d{height:110px;left:-6px;overflow:hidden;position:absolute;top:-6px;width:110px}.rating-banner-3d span{box-shadow:0 5px 4px rgb(0 0 0 / 50%);position:absolute;display:block;width:170px;padding:10px 5px 10px 0;background-color:#ff6a07;color:#fff;font:700 1rem/1 Lato,sans-serif;text-shadow:0 1px 1px rgba(0,0,0,.2);text-transform:uppercase;text-align:center;letter-spacing:1px;right:-20px;top:24px;transform:rotate(-45deg)}.rating-banner-3d::before{top:0;right:0;position:absolute;z-index:-1;content:'';display:block;border:5px solid #a34405;border-top-color:transparent;border-left-color:transparent}.rating-banner-3d::after{bottom:0;left:0;position:absolute;z-index:-1;content:'';display:block;border:5px solid #963e04}"; |
| 46 | + const STASH_ID_ICON_STYLE = |
| 47 | + ".stash-id-count{display:inline-flex;align-items:center;flex-direction:row}.stash-id-count-number{display:inline-block;margin-right:0.25rem}.stash-id-icon{display:inline-flex;align-items:center}.stash-id-icon svg{width:0.875rem;height:0.875rem;fill:currentColor;color:#fff}"; |
45 | 48 |
|
46 | 49 | /** |
47 | 50 | * Element to inject custom CSS styles. |
|
54 | 57 | styleElement.innerHTML += RATING_BANNER_3D_STYLE; |
55 | 58 | if (SETTINGS.performerProfileCards) |
56 | 59 | styleElement.innerHTML += PERFORMER_PROFILE_CARD_STYLE; |
| 60 | + if (SETTINGS.stashIDIcon) |
| 61 | + styleElement.innerHTML += STASH_ID_ICON_STYLE; |
57 | 62 |
|
58 | 63 | function createElementFromHTML(htmlString) { |
59 | 64 | const div = document.createElement("div"); |
|
93 | 98 | } |
94 | 99 |
|
95 | 100 | /** |
96 | | - * Handles gallery cards to specific paths in Stash. |
| 101 | + * Handles gallery cards to specific paths in Stash. |
97 | 102 | * |
98 | 103 | * The supported paths are: |
99 | 104 | * - /galleries |
|
207 | 212 | cards.forEach((card) => { |
208 | 213 | maybeAddFileCount(card, stashData, isContentCard); |
209 | 214 | maybeAddDimensionToBanner(card); |
| 215 | + if (cardClass === "performer-card") { |
| 216 | + maybeAddStashIDIcon(card, stashData); |
| 217 | + |
| 218 | + // Also set up a MutationObserver to watch for card-popovers being added |
| 219 | + if (SETTINGS.stashIDIcon && !card.querySelector(".stash-id-count")) { |
| 220 | + const observer = new MutationObserver((mutations) => { |
| 221 | + const cardPopovers = card.querySelector(".card-popovers.btn-group") || |
| 222 | + card.querySelector(".card-popovers") || |
| 223 | + card.querySelector('[role="group"].btn-group'); |
| 224 | + if (cardPopovers && !cardPopovers.querySelector(".stash-id-count")) { |
| 225 | + const link = card.querySelector(".thumbnail-section > a"); |
| 226 | + if (link) { |
| 227 | + const id = new URL(link.href).pathname.split("/").pop(); |
| 228 | + const idNum = parseInt(id, 10); |
| 229 | + // Query GraphQL for stash IDs |
| 230 | + queryStashIDs(card, id, idNum); |
| 231 | + observer.disconnect(); |
| 232 | + } |
| 233 | + } |
| 234 | + }); |
| 235 | + |
| 236 | + observer.observe(card, { |
| 237 | + childList: true, |
| 238 | + subtree: true |
| 239 | + }); |
| 240 | + |
| 241 | + // Disconnect after 5 seconds to avoid memory leaks |
| 242 | + setTimeout(() => observer.disconnect(), 5000); |
| 243 | + } |
| 244 | + } |
210 | 245 | }); |
211 | 246 | } |
212 | 247 |
|
|
269 | 304 | link.parentElement.appendChild(el); |
270 | 305 | oldBanner.remove(); |
271 | 306 | } |
| 307 | + |
| 308 | + /** |
| 309 | + * Add Stash ID count and icon to performer cards in the card-popovers btn-group |
| 310 | + * |
| 311 | + * @param {Element} card - Card element from cards list. |
| 312 | + * @param {Object} stashData - Data fetched from the GraphQL interceptor. e.g. stash.performers. |
| 313 | + */ |
| 314 | + function maybeAddStashIDIcon(card, stashData) { |
| 315 | + if (!SETTINGS.stashIDIcon) return; |
| 316 | + |
| 317 | + // Verify this function was not run twice on the same card |
| 318 | + const existingCount = card.querySelector(".stash-id-count"); |
| 319 | + if (existingCount) return; |
| 320 | + |
| 321 | + const link = card.querySelector(".thumbnail-section > a"); |
| 322 | + if (!link) return; |
| 323 | + |
| 324 | + const id = new URL(link.href).pathname.split("/").pop(); |
| 325 | + const idNum = parseInt(id, 10); |
| 326 | + |
| 327 | + // Query GraphQL for stash IDs |
| 328 | + queryStashIDs(card, id, idNum); |
| 329 | + } |
| 330 | + |
| 331 | + /** |
| 332 | + * Query GraphQL for performer stash IDs |
| 333 | + * @param {Element} card - Card element |
| 334 | + * @param {string} id - Performer ID as string |
| 335 | + * @param {number} idNum - Performer ID as number |
| 336 | + */ |
| 337 | + async function queryStashIDs(card, id, idNum) { |
| 338 | + const query = ` |
| 339 | + query FindPerformer($id: ID!) { |
| 340 | + findPerformer(id: $id) { |
| 341 | + id |
| 342 | + stash_ids { |
| 343 | + endpoint |
| 344 | + stash_id |
| 345 | + } |
| 346 | + } |
| 347 | + } |
| 348 | + `; |
| 349 | + |
| 350 | + const variables = { |
| 351 | + id: idNum |
| 352 | + }; |
| 353 | + |
| 354 | + try { |
| 355 | + const response = await fetch('/graphql', { |
| 356 | + method: 'POST', |
| 357 | + headers: { |
| 358 | + 'Content-Type': 'application/json', |
| 359 | + }, |
| 360 | + body: JSON.stringify({ |
| 361 | + query: query, |
| 362 | + variables: variables |
| 363 | + }) |
| 364 | + }); |
| 365 | + |
| 366 | + const result = await response.json(); |
| 367 | + |
| 368 | + if (result.errors) return; |
| 369 | + |
| 370 | + const performer = result.data?.findPerformer; |
| 371 | + if (!performer) return; |
| 372 | + |
| 373 | + const stashIDs = performer.stash_ids || []; |
| 374 | + const stashIDCount = Array.isArray(stashIDs) ? stashIDs.length : 0; |
| 375 | + |
| 376 | + // Only show if count is greater than 0 |
| 377 | + if (stashIDCount > 0) { |
| 378 | + // Find card-popovers and add button |
| 379 | + const cardPopovers = card.querySelector(".card-popovers.btn-group") || |
| 380 | + card.querySelector(".card-popovers") || |
| 381 | + card.querySelector('[role="group"].btn-group'); |
| 382 | + |
| 383 | + if (cardPopovers && !cardPopovers.querySelector(".stash-id-count")) { |
| 384 | + addStashIDButton(cardPopovers, stashIDCount); |
| 385 | + } |
| 386 | + } |
| 387 | + } catch (error) { |
| 388 | + // On error, don't show anything (silent fail) |
| 389 | + } |
| 390 | + } |
| 391 | + |
| 392 | + /** |
| 393 | + * Helper function to add the stash ID button to the card-popovers |
| 394 | + */ |
| 395 | + function addStashIDButton(cardPopovers, stashIDCount) { |
| 396 | + // Check if already added |
| 397 | + if (cardPopovers.querySelector(".stash-id-count")) return; |
| 398 | + |
| 399 | + // Box-open icon SVG (StashApp logo style - open box) |
| 400 | + const boxIconSVG = `<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="box-open" class="svg-inline--fa fa-box-open" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="M58.9 42.1c3-6.1 9.6-9.6 16.3-8.7L320 64 564.8 33.4c6.7-.8 13.3 2.7 16.3 8.7l41.7 83.4c9 17.9-.6 39.6-19.8 45.1L439.6 217.3c-13.9 4-28.8-1.9-36.2-14.3L320 64 236.6 203c-7.4 12.4-22.3 18.3-36.2 14.3L37.1 170.6c-19.3-5.5-28.8-27.2-19.8-45.1L58.9 42.1zM321.1 128l54.9 91.4c14.9 24.8 44.6 36.6 72.5 28.6L576 211.6v167c0 22-15 41.2-36.4 46.6l-204.1 51c-10.2 2.6-20.9 2.6-31 0l-204.1-51C79 419.7 64 400.5 64 378.5v-167L191.6 248c27.8 8 57.6-3.8 72.5-28.6L318.9 128h2.2z"></path></svg>`; |
| 401 | + |
| 402 | + // Create a wrapper div similar to the tag-count structure |
| 403 | + const wrapper = document.createElement("div"); |
| 404 | + |
| 405 | + // Create button with count FIRST, then icon (as requested) |
| 406 | + const button = createElementFromHTML( |
| 407 | + `<button type="button" class="minimal stash-id-count btn btn-primary" title="Has ${stashIDCount} Stash ID${stashIDCount !== 1 ? 's' : ''}"> |
| 408 | + <span class="stash-id-count-number">${stashIDCount}</span> |
| 409 | + <span class="stash-id-icon">${boxIconSVG}</span> |
| 410 | + </button>` |
| 411 | + ); |
| 412 | + |
| 413 | + wrapper.appendChild(button); |
| 414 | + cardPopovers.appendChild(wrapper); |
| 415 | + } |
272 | 416 | })(); |
0 commit comments