Frontend for multiple reactions (v1) (#18808)

* Remove extraneous comment (see 9361d2 and 5c18f8)

* Flexible, multiple reaction types

* Fix reaction counts

* Re-use svg for active state for now

* Update yml, update spec

* Possible temp fix for failing tests

* Colorize reaction icon svgs

* Reaction engagement above post title

* Index reactions engagements (for logged-out)

* Maybe readinglist is special

* Try using crayons' dropdown as a drawer?

* readinglist isn't really public now

* feat: update the styles for the reaction drawer

* Grey background highlight, turn off border/shadow

* Read our feature flag docs, saw this was recommended

* Missed fifth emoji: party/tada

* Fix JS test errors

* Update test with tada

* Suppress flashing engagements when no public reactions

* Liberate jump-to-comments from unicorn replacement

* 'Add reaction' on tooltip

* Don't show reaction emoji on index unless it's been used

* rubocop

* Update heart+ total count when toggling

* Do not include 'readinglist' in drawer/public counts

* Fix semi-public readinglist so that icon is badged for current user

* Tweak heart-plus svg

* Style tweak: border on active reaction

* Show reacted icon on drawer trigger for 1.5 sec

* Tweak styles for engagements bar

* Style tweaks for multiple engagements (#index)

* Trying to get size working through crayons/inline_svg

* Sparkle hearts

* Restore unicorn

* Make heart-plus-active work when user has an active reaction

* Try 'hoverdown' a dropdown that activates with hover

* Long touch?

* Tap *outside* the drawer to close

* Mobile reaction drawer is also supposed to be columns

* More reaction count cleanup

* Final emoticons maybe?

* Fix reaction bug when feature disabled

* Remove readinglist from public reaction counts

* Update specs for new reaction categories

* Shuffle makes specs flaky

* Order does not matter

* rubocop

* Update to preserve readinglist analytics

* Shuffle makes specs flaky

* Fix flickering images, remove icon highlight for now

* Don't update total for readinglist

* reactions_by_user_id

* Try renaming this observer function

* Try unid ids for SVGs

* Remove local test file

* Simplistic test for the unique svg transform

* Fix javascript for current SVG

* Signifcant string literals in this case, rubocop

* Use the right expected output

Co-authored-by: Ridhwana <ridhwana.khan16@gmail.com>
This commit is contained in:
Joshua Wehner 2023-01-23 16:00:50 +01:00 committed by GitHub
parent 03bee374d2
commit 30c0485507
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
49 changed files with 999 additions and 110 deletions

View file

@ -4,3 +4,5 @@
//= link_directory ../stylesheets .css
//= link s3_direct_upload.js
//= link katex.css
//= link icon.png
//= link loading-ellipsis.svg

View file

@ -0,0 +1,46 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.412 8.604a2.047 2.047 0 0 0-.321-.028 4.007 4.007 0 0 0-3.987 4.416C2.32 18.278 6.662 22.5 12 22.5c5.477 0 9.917-4.44 9.917-9.916 0-.018-.002-.036-.004-.053-.001-.012-.003-.024-.003-.037l.003-.053c.002-.026.004-.053.004-.08a4.007 4.007 0 0 0-4.007-4.006c-.119 0-.257.014-.401.037a11.12 11.12 0 0 0-.347.578c.216-.026.391-.011.513.058.167.094.062.22-.036.335-.115.136-.219.259.15.302.347.04.53-.03.696-.094.166-.064.313-.121.588-.05.7.181.054.808-.357 1.143-.593.404-.903.245-1.262.06-.229-.117-.477-.245-.832-.245-.07 0-.132.006-.19.014l-.002.002c-.263.036-.425.148-.65.304-.188.128-.418.287-.785.457-.335.155-.757-.037-1.149-.215-.26-.119-.505-.23-.703-.23-.255 0-.503.13-.76.266-.42.221-.866.456-1.41.16a4.177 4.177 0 0 1-.405-.266c-.326-.232-.653-.464-1.125-.452a2.499 2.499 0 0 0-1.06.244c-.226.098-.426.185-.766.174v-.002a2.33 2.33 0 0 1-.304-.03c-1.045-.175-1.66-.431-2.021-.676-.364-.324-.325-.604-.137-.745.146-.11.65-.298 1.001.03.236.22.402.132.633.01l.135-.069a12.54 12.54 0 0 0-.512-.85Z" fill="url(#a)"/>
<path d="M6.924 9.453c-.047.023-.092.047-.135.07-.23.121-.397.209-.633-.011-.352-.328-.855-.139-1-.03-.189.141-.228.421.136.745.362.245.976.501 2.02.676.116.02.213.027.305.03-.135-.355-.362-.88-.692-1.479ZM16.431 10.492c.059-.008.12-.014.191-.014.355 0 .603.128.832.245.36.185.669.344 1.262-.06.411-.335 1.056-.962.357-1.143-.275-.071-.422-.014-.588.05-.165.064-.349.135-.696.094-.369-.043-.265-.166-.15-.302.098-.116.203-.24.036-.335-.122-.07-.297-.084-.513-.058l-.001.003a13.82 13.82 0 0 0-.73 1.52Z" fill="#F19020"/>
<path d="M8.5 15.074c.644 0 1.167-.696 1.167-1.555 0-.86-.523-1.556-1.167-1.556-.645 0-1.167.696-1.167 1.556 0 .859.522 1.555 1.167 1.555Z" fill="url(#b)"/>
<path d="M15.5 15.074c.645 0 1.167-.696 1.167-1.555s-.522-1.556-1.167-1.556c-.644 0-1.167.697-1.167 1.556 0 .859.522 1.555 1.167 1.555Z" fill="url(#c)"/>
<path d="M14.527 18.206c0 1.595-1.131 2.887-2.527 2.887-1.396 0-2.527-1.292-2.527-2.887s1.131-2.888 2.527-2.888c1.396 0 2.527 1.293 2.527 2.888Z" fill="url(#d)"/>
<path d="M5.589 7.491c.088-.034.173-.074.255-.12a1.302 1.302 0 0 0 1.03.241 2.815 2.815 0 0 0 2.398 1.344 2.83 2.83 0 0 0 2.376-1.293c.42.514 1.05.85 1.767.85 1.058 0 1.94-.723 2.202-1.699.01 0 .019.002.027.003.01.002.02.003.031.003.165 0 .326-.018.482-.05.381.324.87.527 1.41.527.347 0 .673-.083.963-.228-.465.48-.971 1.239-.971 1.239-.72-.046-1.43-.31-1.43-.31-.911 1.504-2.953 1.813-4.411.938-2.097 1.768-4.872.366-4.872.366S6.16 8.136 5.59 7.492Z" fill="#CE5C1F"/>
<path d="M6.925 9.457a12.72 12.72 0 0 0-.262-.453c.114.182.183.3.183.3s2.775 1.403 4.872-.365c1.458.875 3.5.565 4.41-.939 0 0 .71.264 1.43.31-.192.278-.238.378-.397.664-.298.533-.543 1.06-.73 1.522-.264.036-.426.148-.652.303a4.897 4.897 0 0 1-.784.458c-.335.155-.757-.037-1.149-.216-.26-.118-.505-.23-.703-.23-.255 0-.503.13-.76.267-.42.221-.866.456-1.41.159a4.157 4.157 0 0 1-.405-.265c-.326-.232-.653-.465-1.125-.452-.531.014-.811.135-1.06.244-.226.098-.426.185-.766.174-.134-.356-.362-.88-.692-1.481Z" fill="url(#e)"/>
<path d="M11.834 3.682a1.257 1.257 0 0 1 .184 2.503 1.312 1.312 0 0 1-1.277 1.02 1.3 1.3 0 0 1-.634-.17 1.307 1.307 0 0 1-2.41.37 2.181 2.181 0 0 1-1.603-1.212 1.174 1.174 0 1 1-.146-1.471 2.183 2.183 0 0 1 2.115-1.656c.575 0 1.095.228 1.486.591a.97.97 0 0 1 1.694.179 1.25 1.25 0 0 1 .591-.154Z" fill="url(#f)"/>
<path d="M18.951 4.864a1.237 1.237 0 1 1-2.474 0 1.237 1.237 0 0 1 2.474 0Z" fill="url(#g)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.256 3.737c.128-.038.26-.064.4-.064a1.437 1.437 0 0 1 0 2.874c-.14 0-.272-.027-.4-.064-.4.493-1.004.814-1.69.814-.54 0-1.028-.203-1.41-.527-.155.033-.316.05-.481.05a.207.207 0 0 1-.03-.002.232.232 0 0 0-.028-.003 2.283 2.283 0 0 1-2.202 1.698 2.275 2.275 0 0 1-1.767-.85 2.83 2.83 0 0 1-2.376 1.293 2.815 2.815 0 0 1-2.397-1.343 1.267 1.267 0 0 1-1.03-.241c-.28.155-.6.248-.942.248a1.969 1.969 0 1 1 0-3.938c.124 0 .245.015.364.037a2.178 2.178 0 0 1 1.92-1.162c.113 0 .22.017.327.034a2.183 2.183 0 0 1 1.837-1.005c.957 0 1.762.62 2.059 1.477a1.304 1.304 0 0 1 2.23.287 2.328 2.328 0 0 1 2.034-1.205c.696 0 1.315.31 1.743.793l.061-.008c.029-.004.058-.008.088-.008.685 0 1.289.322 1.69.815Zm-6.162 1.206a1.26 1.26 0 0 0-1.26-1.26 1.25 1.25 0 0 0-.59.153.971.971 0 0 0-1.695-.178c-.391-.364-.91-.591-1.486-.591a2.183 2.183 0 0 0-2.115 1.655 1.174 1.174 0 1 0 .146 1.471c.301.631.892 1.093 1.603 1.213a1.307 1.307 0 0 0 2.41-.37 1.3 1.3 0 0 0 .634.17c.624 0 1.145-.437 1.278-1.02a1.257 1.257 0 0 0 1.075-1.243Zm4.62 1.158a1.237 1.237 0 1 0 0-2.474 1.237 1.237 0 0 0 0 2.474Z" fill="url(#h)"/>
<defs>
<linearGradient id="a" x1="12" y1="14.155" x2="12" y2="22.499" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCC49"/>
<stop offset="1" stop-color="#FFB649"/>
</linearGradient>
<linearGradient id="b" x1="12" y1="11.962" x2="12" y2="15.074" gradientUnits="userSpaceOnUse">
<stop stop-color="#65471B"/>
<stop offset="1" stop-color="#4E340F"/>
</linearGradient>
<linearGradient id="c" x1="12" y1="11.963" x2="12" y2="15.075" gradientUnits="userSpaceOnUse">
<stop stop-color="#65471B"/>
<stop offset="1" stop-color="#4E340F"/>
</linearGradient>
<linearGradient id="d" x1="12" y1="11.964" x2="12" y2="15.076" gradientUnits="userSpaceOnUse">
<stop stop-color="#65471B"/>
<stop offset="1" stop-color="#4E340F"/>
</linearGradient>
<linearGradient id="e" x1="11.891" y1="6.818" x2="11.891" y2="11.376" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA271"/>
<stop offset="1" stop-color="#F36C24"/>
</linearGradient>
<linearGradient id="f" x1="8.518" y1="3.066" x2="8.518" y2="8.062" gradientUnits="userSpaceOnUse">
<stop stop-color="#EFEFEF"/>
<stop offset="1" stop-color="#E6E6E6"/>
</linearGradient>
<linearGradient id="g" x1="8.518" y1="3.067" x2="8.518" y2="8.063" gradientUnits="userSpaceOnUse">
<stop stop-color="#EFEFEF"/>
<stop offset="1" stop-color="#E6E6E6"/>
</linearGradient>
<linearGradient id="h" x1="12.014" y1="1.586" x2="12.014" y2="8.956" gradientUnits="userSpaceOnUse">
<stop stop-color="#D8D8D8"/>
<stop offset="1" stop-color="#C6C6C6"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.4 KiB

View file

@ -0,0 +1,14 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.31 9.161a9.893 9.893 0 0 1 .607 3.423c0 4.982-3.673 9.105-8.459 9.81a9.888 9.888 0 0 0 5.51-2.757 6.963 6.963 0 0 0-.905-4.136c.087 1.798-1.494 2.504-2.344 2.163-.91-.364-.965-.923-1.061-1.9-.034-.34-.072-.733-.154-1.186-.316-1.75-1.532-2.965-3.33-3.5 1.313 3.694-.727 5.055-1.797 5.299-1.092.248-2.189 0-2.314-2.338a6.98 6.98 0 0 0-2.03 5.598 9.89 9.89 0 0 0 5.524 2.76c-4.793-.699-8.474-4.826-8.474-9.813A9.88 9.88 0 0 1 4.44 6.168c-.849 3.087.072 5.024 1.728 4.86C7.65 10.883 8.282 9.57 7.674 7.99c-.583-1.515-.528-3.858 1.36-5.25-.582 2.625.998 4.243 2.723 4.108 1.556-.121 3.038-2.114 1.361-5.323 4.036 2.115 4.181 4.658 4.043 6.88-.02.315-.098.712-.183 1.144-.27 1.368-.61 3.083.637 3.619 1.75.752 3.427-.863 3.695-4.007Z" fill="url(#a)"/>
<path d="M18.063 15.499c.087 1.799-1.494 2.504-2.344 2.164-1.229-.492-.9-1.337-1.215-3.087-.316-1.75-1.532-2.965-3.33-3.5 1.313 3.694-.727 5.056-1.797 5.3-1.092.248-2.189-.001-2.314-2.338a6.979 6.979 0 0 0-2.03 5.597A9.88 9.88 0 0 0 12 22.5a9.88 9.88 0 0 0 6.968-2.865 6.963 6.963 0 0 0-.905-4.136Z" fill="url(#b)"/>
<defs>
<linearGradient id="a" x1="12" y1="1.525" x2="12" y2="22.501" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA271"/>
<stop offset="1" stop-color="#F36C24"/>
</linearGradient>
<linearGradient id="b" x1="12" y1="15.761" x2="12" y2="22.5" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCC49"/>
<stop offset="1" stop-color="#FFB649"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,11 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1000_2078)">
<path d="M19 14V17H22V19H19V22H17V19H14V17H17V14H19ZM20.243 4.75698C22.403 6.92298 22.572 10.314 20.75 12.667C19.926 12.24 18.99 12 18 12C14.686 12 12 14.686 12 18C12 19.009 12.249 19.96 12.689 20.794L11.999 21.485L3.52101 12.993C1.41701 10.637 1.49601 7.01898 3.75701 4.75698C6.02201 2.49298 9.64501 2.41698 12.001 4.52898C14.35 2.41998 17.98 2.48998 20.243 4.75698Z" fill="#EF4444"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.0765 16.9736V13.9736H17.0765V16.9736H14.0765V18.9736H17.0765V21.9736H19.0765V18.9736H22.0765V16.9736H19.0765ZM20.8271 12.6399C20.1623 12.2963 19.4251 12.0735 18.6444 12.0002C19.4244 12.0734 20.1619 12.2962 20.8265 12.6406C20.8267 12.6404 20.8269 12.6401 20.8271 12.6399ZM12.7654 20.7677C12.6672 20.5815 12.5785 20.3894 12.5 20.1922C12.5786 20.3894 12.6673 20.5814 12.7655 20.7676L12.7654 20.7677Z" fill="#404040"/>
</g>
<defs>
<clipPath id="clip0_1000_2078">
<rect width="24" height="24" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,3 @@
<svg width="20" height="19" viewBox="0 0 20 19" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 11V14H20V16H16.999L17 19H15L14.999 16H12V14H15V11H17ZM18.243 1.75698C20.505 4.02498 20.583 7.63698 18.479 9.99198L17.059 8.57398C18.39 7.04998 18.32 4.65998 16.827 3.16998C15.324 1.67098 12.907 1.60698 11.337 3.01698L10.002 4.21498L8.66603 3.01798C7.09103 1.60598 4.67503 1.66798 3.17203 3.17198C1.68203 4.66198 1.60703 7.04698 2.98003 8.62298L11.412 17.069L10 18.485L1.52003 9.99298C-0.583971 7.63698 -0.50497 4.01898 1.75603 1.75698C4.02103 -0.50702 7.64403 -0.58302 10 1.52898C12.349 -0.58002 15.979 -0.51002 18.242 1.75698H18.243Z" fill="#525252"/>
</svg>

After

Width:  |  Height:  |  Size: 671 B

View file

View file

@ -0,0 +1,60 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.178 11.2a1.125 1.125 0 1 1 0-2.25 1.125 1.125 0 0 1 0 2.25Z" fill="url(#a)"/>
<path d="M17.518 17.126c-.07.384.106.737.391.788.286.052.574-.216.644-.6.07-.385-.106-.738-.391-.79-.286-.052-.574.217-.644.602Z" fill="url(#b)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="m18.394 14.92-.214-.461c-1.296.094-2.863.67-3.127 1.992-.112.557-.469 1.028-.924 1.413.028.06.053.117.078.174.113.256.218.492.5.788 1.334 1.4 3.325.74 4.093-.619.62-1.098.196-2.003-.406-3.288Zm-.485 2.996c-.285-.052-.46-.405-.39-.789.069-.385.357-.653.643-.602.285.052.46.405.391.79-.07.383-.358.652-.644.6Z" fill="url(#c)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="m14.475 6.186.002.001c-.298.265-.288.796.038 1.213.322.412.825.556 1.155.345l-.005.01.027-.023a.112.112 0 0 1 .01-.006.081.081 0 0 0 .014-.008.022.022 0 0 0 .006-.007.027.027 0 0 1 .005-.007l.316-.256.006-.003c.178.195.335.395.46.598h.002c.648 1.22.82 2.491.98 3.66.12.893.234 1.727.545 2.433.122.277.243.536.358.782-.07-.148-.141-.301-.214-.46-1.296.094-2.863.67-3.127 1.991-.112.558-.469 1.029-.924 1.414-.17-.364-.433-.795-1.25-1.49-1.588-.317-2.563-1.045-2.563-2.552 0 0-.464-.223-1.065 3.505-.156.965-.098 1.93-.036 2.934l.017.294c.063 1.057-.796 1.895-1.855 1.895H3.521a1.969 1.969 0 0 1-1.969-1.969v-7.45 3.902c.464-1.023 1-2.468 1.464-3.714.371-.998.694-1.868.894-2.29 1.643-3.476 4.557-6.5 9.001-5.482a7.492 7.492 0 0 1 1.565.739Zm-1.297 5.012a1.125 1.125 0 1 1 0-2.25 1.125 1.125 0 0 1 0 2.25Z" fill="url(#d)"/>
<path d="m16.686 4.673.034-.064.031-.057c.422-.782-.161-.511-.532-.255-.178.124-.855.382-1.522.621.24.148.49.35.75.57.35-.237.775-.516 1.239-.815Z" fill="url(#e)"/>
<path d="M13.878 3.958c.292-.127.524-.227.63-.374.678-.928.487.27.415.617a3.887 3.887 0 0 1-.225.717 5.45 5.45 0 0 1-.597 1.05 7.02 7.02 0 0 0-1.19-.522 8.125 8.125 0 0 0-.804-.143c.534-.81 1.249-1.12 1.771-1.345Z" fill="url(#f)"/>
<path d="M16.049 7.444c.178.195.335.396.46.599 0 0 1.298.328 1.971-.786.067-.11-.043-.236-.17-.222-.458.052-.9-.097-1.322-.344-.5.406-.852.687-.94.753Z" fill="url(#g)"/>
<path d="M15.447 5.487c-.469.316-.805.554-.93.66-.003.003-.006.004-.01.006l-.009.005-.005.007c-.002.003-.003.006-.006.008l-.009.008-.002.003a8.712 8.712 0 0 0-.375-.217 5.44 5.44 0 0 0 .596-1.049c.24.147.49.349.75.57Z" fill="url(#h)"/>
<path d="M13.263 4.25a3.192 3.192 0 0 0-1.156 1.053c-3.995-.5-6.655 2.363-8.197 5.625-.2.422-.523 1.292-.894 2.29-.463 1.245-1 2.69-1.463 3.714V9.779c0-1.26.24-2.596 1.032-3.577a5.7 5.7 0 0 1 .96-.933c.117-.09.282.035.244.178-.213.786-.317 1.869-.195 2.64.087-2.288 1.86-3.839 2.552-4.196 1.208-.623 2.4-.58 3.715-.471.113.01.184.142.146.25-.06.171-.11.413-.081.673.28-.51.935-.828 1.36-.741.77.156 1.45.38 1.977.648Z" fill="url(#i)"/>
<path d="M13.646 5.738c.152-.238.27-.483.37-.693.118-.247.214-.446.316-.532.202-.168-.068-.17-.068-.17-.44.265-.972.662-1.278 1.129.21.07.432.16.66.266Z" fill="url(#j)"/>
<path d="M14.475 6.187h.002c-.298.266-.288.796.038 1.213.322.412.825.556 1.155.346l-.005.01.027-.023a.115.115 0 0 1 .01-.006.081.081 0 0 0 .014-.008.022.022 0 0 0 .006-.007.027.027 0 0 1 .005-.007l.316-.256.006-.004c.11-.084.654-.517 1.38-1.113a2.647 2.647 0 0 1-.634-.613 2.576 2.576 0 0 1-.428-.838l.528-.342c.014.173.1.473.368.831.24.323.479.494.642.57.35-.29.728-.605 1.11-.929a2.775 2.775 0 0 1-.518-.531 2.71 2.71 0 0 1-.385-.707c.17-.106.34-.21.511-.314.044.174.144.408.342.672.18.243.357.397.503.492.36-.309.717-.62 1.05-.92a2.92 2.92 0 0 1-.694-.942l-.005-.014c.176-.101.347-.199.512-.291l.01.027.014.037c.065.141.15.282.25.417.097.131.204.248.316.347l.017.014a.21.21 0 0 1 .015.011c.952-.877 1.619-1.586 1.472-1.733-.417-.416-7.235 4.002-7.909 4.573l-.008.005-.01.006-.005.007-.006.007-.009.008-.003.005Z" fill="url(#k)"/>
<path d="m19.824 2.761-.005-.014c.176-.102.347-.2.512-.292.01.022.013.04.024.064.065.142.15.283.25.418.097.13.204.247.316.347l.033.025-.435.394a2.92 2.92 0 0 1-.695-.942ZM18.497 4.48a2.71 2.71 0 0 1-.385-.707l.511-.314c.044.174.144.408.342.672.18.244.358.398.504.493l-.454.388a2.775 2.775 0 0 1-.518-.532Zm-1.702 1.24a2.576 2.576 0 0 1-.428-.838l.528-.342c.014.172.1.472.368.831.24.323.48.494.642.57l-.475.392a2.646 2.646 0 0 1-.635-.613Z" fill="#EF5E3E"/>
<defs>
<linearGradient id="a" x1="13.178" y1="8.949" x2="13.178" y2="11.199" gradientUnits="userSpaceOnUse">
<stop stop-color="#3A6481"/>
<stop offset="1" stop-color="#1E445E"/>
</linearGradient>
<linearGradient id="b" x1="18.036" y1="16.518" x2="18.036" y2="17.921" gradientUnits="userSpaceOnUse">
<stop stop-color="#061722"/>
<stop offset="1" stop-color="#051622"/>
</linearGradient>
<linearGradient id="c" x1="16.617" y1="14.459" x2="16.617" y2="19.605" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="d" x1="10.329" y1="4.035" x2="10.329" y2="22.449" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3F3FF"/>
<stop offset="1" stop-color="#749CC4"/>
</linearGradient>
<linearGradient id="e" x1="10.329" y1="4.035" x2="10.329" y2="22.449" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3F3FF"/>
<stop offset="1" stop-color="#749CC4"/>
</linearGradient>
<linearGradient id="f" x1="13.322" y1="3.238" x2="13.322" y2="7.148" gradientUnits="userSpaceOnUse">
<stop stop-color="#DBEBF7"/>
<stop offset="1" stop-color="#9AB5C8"/>
</linearGradient>
<linearGradient id="g" x1="10.09" y1="3.355" x2="10.09" y2="16.931" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="h" x1="10.09" y1="3.355" x2="10.09" y2="16.931" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="i" x1="10.09" y1="3.355" x2="10.09" y2="16.932" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="j" x1="13.581" y1="4.344" x2="13.581" y2="6.288" gradientUnits="userSpaceOnUse">
<stop stop-color="#3A6481"/>
<stop offset="1" stop-color="#1E445E"/>
</linearGradient>
<linearGradient id="k" x1="18.353" y1="1.549" x2="18.353" y2="7.842" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA271"/>
<stop offset="1" stop-color="#F36C24"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

@ -0,0 +1,26 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.204 19.032c3 .6 4.8-.6 4.8-.6v2.867a1.5 1.5 0 0 1-1.5 1.5h-1.8a1.5 1.5 0 0 1-1.5-1.5v-2.267Z" fill="url(#a)"/>
<path d="M7.804 19.032c-3 .6-4.8-.6-4.8-.6v2.867a1.5 1.5 0 0 0 1.5 1.5h1.8a1.5 1.5 0 0 0 1.5-1.5v-2.267Z" fill="url(#b)"/>
<path d="M8.394 12.743v-.382h-.062c-2.17.03-3.617 1.842-3.624 3.662 0 .367.367.37.367.37.006-2.082 1.319-3.502 3.319-3.65ZM15.608 12.361c2.204-.005 3.679 1.826 3.686 3.663 0 .367-.367.37-.367.37-.006-2.083-1.32-3.502-3.32-3.65v-.383Z" fill="#FFAC2B"/>
<path d="M1.428 8.3s.002-.735.736-.733c.736.002.733.737.733.737l-.011 3.41c.117-.053.24-.102.368-.144l.015-4.733s.002-.735.736-.733c.734.002.733.737.733.737l-.013 4.452c.12-.01.242-.019.366-.025l.017-5.161s.002-.735.737-.732c.735.002.732.737.732.737l-.017 5.16c.126.008.247.02.368.032l.015-4.824s.003-.735.736-.732c.735.002.732.736.732.736l-.015 5.101-.001.409v.366c-2.205-.005-3.68 1.825-3.687 3.663 0 .367.367.37.367.37.006-2.208 1.48-3.67 3.686-3.663l1.08-2.513s.237-.695.934-.457c.695.235.459.932.459.932l-.536 1.863c-.212.74-.411 1.485-.515 2.246a4.412 4.412 0 0 1-4.38 3.798 4.41 4.41 0 0 1-4.393-4.422c0-.128.008-.25.02-.366l-.019-.002.017-5.509Zm21.146 0s-.001-.735-.736-.733c-.735.002-.734.737-.734.737l.012 3.41a3.386 3.386 0 0 0-.367-.144l-.015-4.733s-.003-.735-.737-.733c-.735.002-.733.737-.733.737l.013 4.452a8.695 8.695 0 0 0-.366-.025l-.016-5.161s-.002-.735-.737-.732c-.735.002-.732.737-.732.737l.017 5.16c-.126.008-.248.02-.369.032L17.06 6.48s-.003-.735-.737-.732c-.734.002-.731.736-.731.736l.014 5.101.002.409v.366c2.205-.005 3.68 1.825 3.687 3.663 0 .367-.367.37-.367.37-.007-2.208-1.48-3.67-3.685-3.663l-1.08-2.513s-.237-.695-.934-.457c-.695.235-.46.932-.46.932l.536 1.863c.213.74.411 1.485.516 2.246a4.412 4.412 0 0 0 4.38 3.798 4.41 4.41 0 0 0 4.392-4.422c0-.128-.008-.25-.02-.366l.02-.002-.018-5.509Z" fill="url(#c)"/>
<path d="M3.004 17.582v.849s1.8 1.2 4.8.6v-.906a4.397 4.397 0 0 1-4.8-.543ZM16.204 18.129a4.397 4.397 0 0 0 4.8-.55v.853s-1.8 1.2-4.8.6v-.903Z" fill="#FFAC2B"/>
<path d="M15.329 5.074a.6.6 0 0 1-.474-.967l1.787-2.313a.6.6 0 1 1 .95.734l-1.788 2.313a.597.597 0 0 1-.475.233Zm-6.612 0a.6.6 0 0 1-.475-.233L6.454 2.528a.6.6 0 0 1 .95-.734l1.788 2.313a.6.6 0 0 1-.475.967Zm3.287-.275a.6.6 0 0 1-.6-.6V1.8a.6.6 0 0 1 1.2 0v2.4a.6.6 0 0 1-.6.6Z" fill="url(#d)"/>
<defs>
<linearGradient id="a" x1="12.004" y1="19.261" x2="12.004" y2="22.8" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCC49"/>
<stop offset="1" stop-color="#FFB649"/>
</linearGradient>
<linearGradient id="b" x1="12.004" y1="19.261" x2="12.004" y2="22.8" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCC49"/>
<stop offset="1" stop-color="#FFB649"/>
</linearGradient>
<linearGradient id="c" x1="12.001" y1="10.798" x2="12.001" y2="18.599" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFCC49"/>
<stop offset="1" stop-color="#FFB649"/>
</linearGradient>
<linearGradient id="d" x1="12.023" y1="1.199" x2="12.023" y2="5.074" gradientUnits="userSpaceOnUse">
<stop stop-color="#5DADEC"/>
<stop offset="1" stop-color="#7870F5"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

View file

@ -0,0 +1,12 @@
<svg fill="none" height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg">
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="15.908" x2="15.908" y1="3.199" y2="28.854">
<stop offset="0" stop-color="#fd556a"/>
<stop offset="1" stop-color="#aa172a"/>
</linearGradient>
<linearGradient id="b" gradientUnits="userSpaceOnUse" x1="16.443" x2="16.443" y1="4.828" y2="26.958">
<stop offset="0" stop-color="#ffdc5d"/>
<stop offset="1" stop-color="#ffcc14"/>
</linearGradient>
<path d="M30.216 11.094a7.894 7.894 0 0 0-7.894-7.895 7.88 7.88 0 0 0-6.414 3.303A7.88 7.88 0 0 0 9.494 3.2 7.894 7.894 0 0 0 1.6 11.094c0 .617.078 1.216.213 1.792 1.096 6.81 8.668 13.996 14.095 15.968 5.426-1.972 13-9.157 14.094-15.967a7.796 7.796 0 0 0 .214-1.793z" fill="url(#a)"/>
<path d="m28.986 20.743-3.06-1.132-1.132-3.06a.8.8 0 0 0-1.501 0L22.16 19.61l-3.06 1.133a.8.8 0 0 0 0 1.5l3.06 1.133 1.133 3.06a.8.8 0 0 0 1.5 0l1.133-3.06 3.06-1.133a.8.8 0 0 0 0-1.5zm-18.4-12.8-1.892-.7-.7-1.892a.8.8 0 0 0-1.501 0l-.701 1.892-1.891.7a.8.8 0 0 0 0 1.5l1.892.7.7 1.892a.8.8 0 0 0 1.5 0l.7-1.892 1.893-.7a.8.8 0 0 0 0-1.5z" fill="url(#b)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<path fill="#FFDC5D" d="M3 26h8v10H3zm22 0h8v10h-8z"/>
<path fill="#F9CA55" d="M33 28.72s-3 2-8 1v-5h8v4zm-30 0s3 2 8 1v-5H3v4z"/>
<path fill="#EF9645" d="M3.983 18.604h8v8h-8zm20.023-.5h8v8h-8z"/>
<path fill="#FFDC5D" d="M.373 11.835S.376 10.61 1.6 10.613c1.226.004 1.222 1.229 1.222 1.229l-.019 5.684a5.66 5.66 0 0 1 .613-.241l.025-7.889s.004-1.225 1.227-1.221c1.224.003 1.221 1.229 1.221 1.229l-.021 7.42c.199-.018.404-.032.61-.042l.028-8.602s.003-1.225 1.228-1.22c1.225.004 1.22 1.229 1.22 1.229l-.028 8.6c.21.012.412.033.614.052l.025-8.039s.004-1.225 1.227-1.22c1.224.003 1.219 1.227 1.219 1.227l-.024 8.501-.003.681v.611c-3.674-.009-6.133 3.042-6.144 6.104 0 .612.612.616.612.616.01-3.678 2.467-6.115 6.142-6.104l1.801-4.188s.395-1.159 1.556-.762c1.158.392.765 1.553.765 1.553l-.893 3.105c-.354 1.234-.685 2.476-.859 3.744A7.353 7.353 0 0 1 7.665 29C3.61 28.983.33 25.685.343 21.63a6.16 6.16 0 0 1 .034-.61l-.032-.004.028-9.181zm35.244 0s-.003-1.225-1.227-1.222c-1.226.004-1.223 1.229-1.223 1.229l.019 5.684a5.644 5.644 0 0 0-.612-.241l-.025-7.889s-.004-1.225-1.227-1.221c-1.225.003-1.222 1.229-1.222 1.229l.022 7.42a14.492 14.492 0 0 0-.61-.042l-.027-8.602s-.004-1.225-1.229-1.22c-1.225.004-1.22 1.229-1.22 1.229l.028 8.6c-.21.012-.412.033-.614.052l-.025-8.039s-.005-1.225-1.228-1.22c-1.224.003-1.219 1.227-1.219 1.227l.024 8.501.003.681v.611c3.674-.009 6.133 3.042 6.145 6.104 0 .612-.612.616-.612.616-.011-3.678-2.468-6.115-6.142-6.104l-1.801-4.188s-.394-1.159-1.556-.762c-1.157.392-.765 1.553-.765 1.553l.893 3.105c.354 1.234.685 2.476.859 3.744A7.353 7.353 0 0 0 28.325 29c4.055-.017 7.336-3.315 7.322-7.37a6.16 6.16 0 0 0-.034-.61l.032-.004-.028-9.181z"/>
<path d="M23.541 6.458a1 1 0 0 1-.79-1.611L25.73.992a1 1 0 1 1 1.582 1.223L24.333 6.07a.995.995 0 0 1-.792.388zm-11.02 0a.999.999 0 0 1-.792-.389L8.75 2.215A1.001 1.001 0 0 1 10.334.992l2.979 3.855a1.001 1.001 0 0 1-.792 1.611zM18 6a1 1 0 0 1-1-1V1a1 1 0 0 1 2 0v4a1 1 0 0 1-1 1z" fill="#5DADEC"/>
</svg>

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<path fill="#A0041E" d="m1 17 8-7 16 1 1 16-7 8s.001-5.999-6-12-12-6-12-6z"/>
<path fill="#FFAC33" d="M.973 35s-.036-7.979 2.985-11S15 21.187 15 21.187 14.999 29 11.999 32c-3 3-11.026 3-11.026 3z"/>
<circle fill="#FFCC4D" cx="8.999" cy="27" r="4"/>
<path fill="#55ACEE" d="M35.999 0s-10 0-22 10c-6 5-6 14-4 16s11 2 16-4c10-12 10-22 10-22z"/>
<path d="M26.999 5a3.996 3.996 0 0 0-3.641 2.36A3.969 3.969 0 0 1 24.999 7a4 4 0 0 1 4 4c0 .586-.133 1.139-.359 1.64A3.993 3.993 0 0 0 30.999 9a4 4 0 0 0-4-4z"/>
<path fill="#A0041E" d="M8 28s0-4 1-5 13.001-10.999 14-10-9.001 13-10.001 14S8 28 8 28z"/>
</svg>

After

Width:  |  Height:  |  Size: 687 B

View file

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<path fill="#DD2E44" d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868-3.308 0-6.227 1.633-8.018 4.129-1.791-2.496-4.71-4.129-8.017-4.129-5.45 0-9.868 4.417-9.868 9.868 0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959.17-.721.268-1.469.268-2.242z"/>
<path fill="#FDCB58" d="m34.347 23.894-3.824-1.416-1.416-3.824a1 1 0 0 0-1.876-.001l-1.416 3.824-3.824 1.416a1.001 1.001 0 0 0 0 1.876l3.824 1.416 1.416 3.824a1 1 0 0 0 1.876 0l1.416-3.824 3.824-1.416a1.001 1.001 0 0 0 0-1.875zm-23-16.001-2.365-.875-.875-2.365a1 1 0 0 0-1.876 0l-.876 2.365-2.364.875a1 1 0 0 0 0 1.876l2.365.875.875 2.365a1 1 0 0 0 1.876 0l.875-2.365 2.365-.875a1 1 0 0 0 0-1.876z"/>
</svg>

After

Width:  |  Height:  |  Size: 780 B

View file

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36">
<path fill="#DD2E44" d="M11.626 7.488a1.413 1.413 0 0 0-.268.395l-.008-.008L.134 33.141l.011.011c-.208.403.14 1.223.853 1.937.713.713 1.533 1.061 1.936.853l.01.01L28.21 24.735l-.008-.009c.147-.07.282-.155.395-.269 1.562-1.562-.971-6.627-5.656-11.313-4.687-4.686-9.752-7.218-11.315-5.656z"/>
<path fill="#EA596E" d="M13 12 .416 32.506l-.282.635.011.011c-.208.403.14 1.223.853 1.937.232.232.473.408.709.557L17 17l-4-5z"/>
<path fill="#A0041E" d="M23.012 13.066c4.67 4.672 7.263 9.652 5.789 11.124-1.473 1.474-6.453-1.118-11.126-5.788-4.671-4.672-7.263-9.654-5.79-11.127 1.474-1.473 6.454 1.119 11.127 5.791z"/>
<path fill="#AA8DD8" d="M18.59 13.609a.99.99 0 0 1-.734.215c-.868-.094-1.598-.396-2.109-.873-.541-.505-.808-1.183-.735-1.862.128-1.192 1.324-2.286 3.363-2.066.793.085 1.147-.17 1.159-.292.014-.121-.277-.446-1.07-.532-.868-.094-1.598-.396-2.11-.873-.541-.505-.809-1.183-.735-1.862.13-1.192 1.325-2.286 3.362-2.065.578.062.883-.057 1.012-.134.103-.063.144-.123.148-.158.012-.121-.275-.446-1.07-.532a.998.998 0 0 1-.886-1.102.997.997 0 0 1 1.101-.886c2.037.219 2.973 1.542 2.844 2.735-.13 1.194-1.325 2.286-3.364 2.067-.578-.063-.88.057-1.01.134-.103.062-.145.123-.149.157-.013.122.276.446 1.071.532 2.037.22 2.973 1.542 2.844 2.735-.129 1.192-1.324 2.286-3.362 2.065-.578-.062-.882.058-1.012.134-.104.064-.144.124-.148.158-.013.121.276.446 1.07.532a1 1 0 0 1 .52 1.773z"/>
<path fill="#77B255" d="M30.661 22.857c1.973-.557 3.334.323 3.658 1.478.324 1.154-.378 2.615-2.35 3.17-.77.216-1.001.584-.97.701.034.118.425.312 1.193.095 1.972-.555 3.333.325 3.657 1.479.326 1.155-.378 2.614-2.351 3.17-.769.216-1.001.585-.967.702.033.117.423.311 1.192.095a1 1 0 1 1 .54 1.925c-1.971.555-3.333-.323-3.659-1.479-.324-1.154.379-2.613 2.353-3.169.77-.217 1.001-.584.967-.702-.032-.117-.422-.312-1.19-.096-1.974.556-3.334-.322-3.659-1.479-.325-1.154.378-2.613 2.351-3.17.768-.215.999-.585.967-.701-.034-.118-.423-.312-1.192-.096a1 1 0 1 1-.54-1.923z"/>
<path fill="#AA8DD8" d="M23.001 20.16a1.001 1.001 0 0 1-.626-1.781c.218-.175 5.418-4.259 12.767-3.208a1 1 0 1 1-.283 1.979c-6.493-.922-11.187 2.754-11.233 2.791a.999.999 0 0 1-.625.219z"/>
<path fill="#77B255" d="M5.754 16a1 1 0 0 1-.958-1.287c1.133-3.773 2.16-9.794.898-11.364-.141-.178-.354-.353-.842-.316-.938.072-.849 2.051-.848 2.071a1 1 0 1 1-1.994.149c-.103-1.379.326-4.035 2.692-4.214 1.056-.08 1.933.287 2.552 1.057 2.371 2.951-.036 11.506-.542 13.192a1 1 0 0 1-.958.712z"/>
<circle fill="#5C913B" cx="25.5" cy="9.5" r="1.5"/>
<circle fill="#9266CC" cx="2" cy="18" r="2"/>
<circle fill="#5C913B" cx="32.5" cy="19.5" r="1.5"/>
<circle fill="#5C913B" cx="23.5" cy="31.5" r="1.5"/>
<circle fill="#FFCC4D" cx="28" cy="4" r="2"/>
<circle fill="#FFCC4D" cx="32.5" cy="8.5" r="1.5"/>
<circle fill="#FFCC4D" cx="29.5" cy="12.5" r="1.5"/>
<circle fill="#FFCC4D" cx="7.5" cy="23.5" r="1.5"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

@ -1,19 +1,29 @@
/* global sendHapticMessage, showLoginModal, showModalAfterError */
/* global sendHapticMessage, showLoginModal, showModalAfterError, isTouchDevice, watchForLongTouch */
// Set reaction count to correct number
function setReactionCount(reactionName, newCount) {
var reactionClassList = document.getElementById(
var reactionButtons = document.getElementById(
'reaction-butt-' + reactionName,
).classList;
var reactionNumber = document.getElementById(
var reactionButtonCounter = document.getElementById(
'reaction-number-' + reactionName,
);
var reactionEngagementCounter = document.getElementById(
'reaction_engagement_' + reactionName + '_count',
);
if (newCount > 0) {
reactionClassList.add('activated');
reactionNumber.textContent = newCount;
reactionButtons.add('activated');
reactionButtonCounter.textContent = newCount;
if (reactionEngagementCounter) {
reactionEngagementCounter.parentElement.classList.remove('hidden');
reactionEngagementCounter.textContent = newCount;
}
} else {
reactionClassList.remove('activated');
reactionNumber.textContent = '0';
reactionButtons.remove('activated');
reactionButtonCounter.textContent = '0';
if (reactionEngagementCounter) {
reactionEngagementCounter.parentElement.classList.add('hidden');
}
}
}
@ -23,6 +33,41 @@ function showUserReaction(reactionName, animatedClass) {
);
reactionButton.classList.add('user-activated', animatedClass);
reactionButton.setAttribute('aria-pressed', 'true');
const reactionDrawerButton = document.getElementById(
'reaction-drawer-trigger',
);
// the rest only applies to multiple reactions feature flag
if (!reactionDrawerButton) {
return;
}
if (reactionDrawerButton && reactionName !== 'readinglist') {
reactionDrawerButton.classList.add('user-activated', 'user-animated');
}
if (animatedClass == 'user-animated') {
const activeIcon = reactionButton.querySelector(
'.crayons-reaction__icon--active svg',
);
if (activeIcon) {
const activeDrawerIcon = reactionDrawerButton.querySelector(
'.crayons-reaction__icon--active img',
);
reactionDrawerButton.originalIcon = activeDrawerIcon.outerHTML;
activeDrawerIcon.outerHTML = activeIcon.outerHTML;
setTimeout(function () {
document
.getElementById('reaction-drawer-trigger')
.querySelector('.crayons-reaction__icon--active svg').outerHTML =
reactionDrawerButton.originalIcon;
}, 1500);
}
}
}
function hideUserReaction(reactionName) {
@ -49,15 +94,25 @@ function getNumReactions(reactionName) {
}
function reactToArticle(articleId, reaction) {
var reactionTotalCount = document.getElementById('reaction_total_count');
var isReadingList = reaction === 'readinglist';
// Visually toggle the reaction
function toggleReaction() {
var currentNum = getNumReactions(reaction);
if (hasUserReacted(reaction)) {
hideUserReaction(reaction);
setReactionCount(reaction, currentNum - 1);
if (reactionTotalCount && !isReadingList) {
reactionTotalCount.innerText = Number(reactionTotalCount.innerText) - 1;
}
} else {
showUserReaction(reaction, 'user-animated');
setReactionCount(reaction, currentNum + 1);
if (reactionTotalCount && !isReadingList) {
reactionTotalCount.innerText = Number(reactionTotalCount.innerText) + 1;
}
}
}
var userStatus = document.body.getAttribute('data-user-status');
@ -153,9 +208,47 @@ function requestReactionCounts(articleId) {
ajaxReq.send();
}
function openDrawerOnHover() {
var timer;
const drawerTrigger = document.getElementById('reaction-drawer-trigger');
if (!drawerTrigger) {
return;
}
drawerTrigger.addEventListener('click', function (event) {
event.preventDefault();
});
if (isTouchDevice()) {
watchForLongTouch(drawerTrigger);
drawerTrigger.addEventListener('longTouch', function () {
drawerTrigger.parentElement.classList.add('open');
});
document.addEventListener('touchstart', function (event) {
if (!drawerTrigger.parentElement.contains(event.target)) {
drawerTrigger.parentElement.classList.remove('open');
}
});
} else {
document.querySelectorAll('.hoverdown').forEach(function (el) {
el.addEventListener('mouseover', function (event) {
this.classList.add('open');
clearTimeout(timer);
});
el.addEventListener('mouseout', function (event) {
timer = setTimeout(function (event) {
document.querySelector('.hoverdown.open').classList.remove('open');
}, 1000);
});
});
}
}
function initializeArticleReactions() {
setCollectionFunctionality();
openDrawerOnHover();
setTimeout(() => {
var reactionButts = document.getElementsByClassName('crayons-reaction');
@ -167,6 +260,9 @@ function initializeArticleReactions() {
requestReactionCounts(articleId);
for (var i = 0; i < reactionButts.length; i += 1) {
if (reactionButts[i].classList.contains('pseudo-reaction')) {
continue;
}
reactionButts[i].onclick = function addReactionOnClick(e) {
reactToArticle(articleId, this.dataset.category);
};
@ -174,13 +270,15 @@ function initializeArticleReactions() {
}
var jumpToCommentsButt = document.getElementById('reaction-butt-comment');
var commentsSection = document.getElementById("comments");
if (document.getElementById('article-body') && commentsSection && jumpToCommentsButt) {
var commentsSection = document.getElementById('comments');
if (
document.getElementById('article-body') &&
commentsSection &&
jumpToCommentsButt
) {
jumpToCommentsButt.onclick = function jumpToComments(e) {
commentsSection.scrollIntoView({behavior: "smooth"});
commentsSection.scrollIntoView({ behavior: 'smooth' });
};
}
}, 3);
}

View file

@ -0,0 +1,10 @@
/**
* Determines whether or not a device is a touch device.
*
* @returns true if a touch device, otherwise false.
*/
function isTouchDevice() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|DEV-Native-ios/i.test(
navigator.userAgent,
);
}

View file

@ -0,0 +1,40 @@
// Watch @param element for a "long touch" (800ms) and dispatch a custom "longTouch" event,
// which you are presumably listening for elsewhere. Note that this also disables context menu
// for the element, as otherwise device will default to opening a context menu instead.
function watchForLongTouch(element) {
const longTouchEvent = new Event('longTouch');
const minDuration = 800;
let timer;
// In the event of a long touch, dispatch the "longTouch" event
const dispatcher = function () {
element.dispatchEvent(longTouchEvent);
};
// Observe "touchstart", if it's still going after a duration, dispatch "longTouch"
const onTouchStart = function (event) {
if (!timer) {
timer = setTimeout(dispatcher, minDuration);
}
};
// Observe "touchend", if they've stopped touching, it won't become a longTouch anytime soon
const onTouchEnd = function () {
if (timer) {
clearTimeout(timer);
timer = false;
}
};
if (element) {
// Disable contextmenu on device, otherwise they'll get a device OS menu instead of ours
element.oncontextmenu = function (event) {
event.preventDefault();
event.stopPropagation(); // not necessary in my case, could leave in case stopImmediateProp isn't available?
event.stopImmediatePropagation();
return false;
};
element.addEventListener('touchstart', onTouchStart);
element.addEventListener('touchend', onTouchEnd);
}
}

View file

@ -36,6 +36,7 @@
--radius: 0.375rem;
--radius-large: 0.75rem;
--radius-xl: 2rem;
// 1. this is a trick for conditional border-radius: https://ishadeed.com/article/conditional-border-radius/
// 2. intentionally using uppercase Max and Min because SCSS have issues otherwise...
--radius-auto: Max(0px, Min(var(--radius), calc((100vw - 4px - 100%) * 9999))) /

View file

@ -241,6 +241,23 @@
}
}
// This overrides some of the styles for the temporarily feature-flaged
.reaction-drawer .crayons-reaction {
border: 1px solid var(--card-bg);
flex-direction: column;
&.user-activated {
background-color: var(--body-bg);
border: 1px solid #d4d4d4;
border-radius: 20px;
.crayons-reaction__icon--active {
border: 0;
box-shadow: none;
}
}
}
.user-animated {
.crayons-reaction__icon {
position: relative;
@ -349,3 +366,107 @@
color: var(--card-color-tertiary);
}
}
// TODO: The drawer__outer contains the trigger button and the drawer itself,
// enabling hover, however this then requires the __outer wrapping div to be
// styled similarly to the other action buttons in the sidebar, which we can
// clean up significantly *after* the multiple_reactions feature flag is
// fully rolled-out.
.reaction-drawer__outer {
border: none;
padding: 0;
background: transparent;
display: inline-flex;
flex-direction: row;
align-items: center;
@media (min-width: $breakpoint-m) {
flex-direction: column;
}
&.open .reaction-drawer {
display: block;
}
.hidden {
display: none;
}
}
.reaction-drawer {
min-width: auto;
border-radius: 32px;
position: absolute;
top: -80px;
left: 15px;
display: none; // Initially hidden
// Copying from Dropdown for now
padding: var(--su-2);
min-width: 250px;
margin-top: var(--su-1);
z-index: var(--z-dropdown);
background: var(--card-bg);
color: var(--card-color);
box-shadow: var(--shadow-1);
border-radius: var(--radius);
flex-direction: column;
@media (min-width: $breakpoint-s) {
max-width: 360px;
width: max-content;
top: -5px;
left: 55px;
}
.crayons-reaction__count {
min-width: auto;
margin-left: 0;
display: block;
}
}
.reaction-drawer__outer:hover .reaction-drawer {
@media (min-width: $breakpoint-m) {
display: block;
}
}
.reaction-drawer__container {
display: flex;
flex-direction: row;
gap: var(--su-3);
}
.multiple_reactions_engagement {
padding: 0.5em 0;
span {
margin-right: 1em;
}
svg {
display: inline-block;
}
}
.multiple_reactions_aggregate {
padding-right: 1em;
.crayons_icon_container {
display: inline-block;
background-color: var(--body-bg);
border-radius: 15px;
margin-right: -0.75em;
width: 24px;
height: 24px;
}
svg {
margin: 3px 3px;
}
.aggregate_reactions_counter {
margin-left: 0.75em;
}
}

View file

@ -12,16 +12,8 @@ class ReactionsController < ApplicationController
if params[:article_id]
id = params[:article_id]
reactions = if session_current_user_id
Reaction.public_category
.where(
reactable_id: id,
reactable_type: "Article",
user_id: session_current_user_id,
)
else
Reaction.none
end
reactions = reactions_by_user_id(session_current_user_id) if session_current_user_id
reactions ||= Reaction.none
result = { article_reaction_counts: Reaction.count_for_article(id) }
else
@ -87,6 +79,23 @@ class ReactionsController < ApplicationController
authorize(Reaction, policy_query)
end
def reactions_by_user_id(user_id = session_current_user_id)
id = params[:article_id]
public_reactions = Reaction.public_category.where(
reactable_id: id,
reactable_type: "Article",
user_id: user_id,
)
readinglist = Reaction.where(
reactable_id: id,
reactable_type: "Article",
category: "readinglist", user_id: user_id
)
public_reactions + readinglist
end
# TODO: should this move to toggle service? refactor?
def remove_count_cache_key
return unless params[:reactable_type] == "Article"

View file

@ -29,6 +29,9 @@ module CrayonsHelper
opts.delete(:class),
].compact.join(" ")
inline_svg_tag(icon_name, aria: true, width: 24, height: 24, class: icon_class, **opts)
opts[:width] ||= 24
opts[:height] ||= 24
inline_svg_tag(icon_name, aria: true, class: icon_class, **opts)
end
end

View file

@ -34,6 +34,20 @@ if (snackZone) {
// eslint-disable-next-line no-restricted-globals
top.addSnackbarItem = addSnackbarItem;
const multiReactionDrawerTrigger = document.getElementById(
'reaction-drawer-trigger',
);
if (
multiReactionDrawerTrigger &&
multiReactionDrawerTrigger.dataset.initialized !== 'true'
) {
initializeDropdown({
triggerElementId: 'reaction-drawer-trigger',
dropdownContentId: 'reaction-drawer',
});
}
// Dropdown accessibility
function hideCopyLinkAnnouncerIfVisible() {
document.getElementById('article-copy-link-announcer').hidden = true;

View file

@ -1,3 +1,5 @@
/* global isTouchDevice */
function closeHeaderMenu(memberMenu, menuNavButton) {
menuNavButton.setAttribute('aria-expanded', 'false');
memberMenu.classList.remove('desktop', 'showing');
@ -28,17 +30,6 @@ function openHeaderMenu(memberMenu, menuNavButton) {
})();
}
/**
* Determines whether or not a device is a touch device.
*
* @returns true if a touch device, otherwise false.
*/
export function isTouchDevice() {
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|DEV-Native-ios/i.test(
navigator.userAgent,
);
}
/**
* Initializes the member navigation menu events.
*

View file

@ -8,4 +8,8 @@ module Reactable
def sync_reactions_count
update_column(:public_reactions_count, reactions.public_category.size)
end
def reaction_categories
reactions.distinct(:category).pluck(:category)
end
end

View file

@ -7,11 +7,16 @@ class Reaction < ApplicationRecord
counter_culture :reactable,
column_name: proc { |model|
ReactionCategory[model.category].visible_to_public? ? "public_reactions_count" : "reactions_count"
# After FeatureFlag :multiple_reactions, this could change to:
# ReactionCategory[model.category].visible_to_public?
public_reaction_types.include?(model.category.to_s) ? "public_reactions_count" : "reactions_count"
}
counter_culture :user
scope :public_category, -> { where(category: ReactionCategory.public.map(&:to_s)) }
scope :public_category, lambda {
categories = public_reaction_types
where(category: categories)
}
# Be wary, this is all things on the reading list, but for an end
# user they might only see readinglist items that are published.
@ -55,10 +60,8 @@ class Reaction < ApplicationRecord
reactions = Reaction.where(reactable_id: id, reactable_type: "Article")
counts = reactions.group(:category).count
reaction_types = %w[like readinglist]
unless FeatureFlag.enabled?(:replace_unicorn_with_jump_to_comments)
reaction_types << "unicorn"
end
reaction_types = public_reaction_types
reaction_types << "readinglist" unless public_reaction_types.include?("readinglist")
reaction_types.map do |type|
{ category: type, count: counts.fetch(type, 0) }
@ -75,6 +78,27 @@ class Reaction < ApplicationRecord
end
end
def public_reaction_types
if FeatureFlag.enabled?(:multiple_reactions)
reaction_types = ReactionCategory.public.map(&:to_s) - ["readinglist"]
else
# used to include "readinglist" but that's not correct now, even without the feature flag
# we aren't going to re-process these, they will gradually correct over time
reaction_types = %w[like]
unless FeatureFlag.enabled?(:replace_unicorn_with_jump_to_comments)
reaction_types << "unicorn"
end
end
reaction_types
end
def for_analytics
reaction_types = public_reaction_types
reaction_types << "readinglist" unless public_reaction_types.include?("readinglist")
where(category: reaction_types)
end
# @param user [User] the user who might be spamming the system
# @param threshold [Integer] the number of strikes before they are spam
# @param include_user_profile [Boolean] do we include the user's profile as part of the "check

View file

@ -13,6 +13,10 @@ class ReactionCategory
list.filter_map { |category| category.slug if category.privileged? && category.negative? }
end
def for_view
list.sort_by(&:position).filter_map { |category| category if category.visible_to_public? }
end
def public
list.sort_by(&:position).filter_map { |category| category.slug if category.visible_to_public? }
end

View file

@ -91,7 +91,7 @@ class AnalyticsService
.where("score > 0")
@follow_data = Follow
.where(followable_type: user_or_org.class.name, followable_id: user_or_org.id)
@reaction_data = Reaction.public_category
@reaction_data = Reaction.for_analytics
.where(reactable_id: article_ids, reactable_type: "Article")
@page_view_data = PageView.where(article_id: article_ids)

View file

@ -1,51 +1,11 @@
<div class="crayons-article-actions print-hidden">
<div class="crayons-article-actions__inner">
<% if @article.published? %>
<%= render partial: "articles/reaction_button",
locals: {
category: :like,
description: t("views.reactions.like.title"),
image_path: "heart.svg",
image_active_path: "heart-filled.svg",
aria_label: t("views.reactions.like.aria_label")
} %>
<% if FeatureFlag.enabled?(:replace_unicorn_with_jump_to_comments) %>
<button
id="reaction-butt-comment"
aria-label="<%= t("views.articles.jump_to_comments.aria_label") %>"
aria-pressed="false"
class="crayons-reaction crayons-reaction--comment crayons-tooltip__activator relative"
data-category="comment">
<span class="crayons-reaction__icon crayons-reaction__icon--inactive">
<%= crayons_icon_tag("comment.svg", aria_hidden: true) %>
</span>
<span class="crayons-reaction__count" id="reaction-number-comment"><%= @article.comments_count %></span>
<span data-testid="tooltip" class="crayons-tooltip__content">
<%= t("views.articles.jump_to_comments.label") %>
</span>
</button>
<% if FeatureFlag.enabled?(:multiple_reactions) %>
<%= render partial: "articles/multiple_reactions" %>
<% else %>
<%= render partial: "articles/reaction_button",
locals: {
category: :unicorn,
description: t("views.reactions.unicorn.title"),
image_path: "unicorn.svg",
image_active_path: "unicorn-filled.svg",
aria_label: t("views.reactions.unicorn.aria_label")
} %>
<%= render partial: "articles/original_reactions" %>
<% end %>
<%= render partial: "articles/reaction_button",
locals: {
category: :readinglist,
description: t("views.reactions.readingList.title"),
image_path: "save.svg",
image_active_path: "save-filled.svg",
aria_label: t("views.reactions.readingList.aria_label")
} %>
<% end %>
<div id="mod-actions-menu-btn-area" class="print-hidden hidden align-center"></div>
<div class="align-center m:relative">

View file

@ -0,0 +1,10 @@
<div class="multiple_reactions_engagement">
<% ReactionCategory.for_view.each do |reaction_type| %>
<span class="reaction_engagement_<%= reaction_type.slug %> hidden">
<%= crayons_icon_tag(reaction_type.icon, native: true, aria_hidden: true, force_unique: true) %>
<span id="reaction_engagement_<%= reaction_type.slug %>_count">&nbsp;</span>
</span>
<% end %>
<span class="placeholder" style="width: 24px; height: 24px;">&nbsp;</span>
</div>

View file

@ -0,0 +1,61 @@
<div class="reaction-drawer__outer hoverdown" style="">
<button
id="reaction-drawer-trigger"
aria-label="reaction-drawer-trigger"
aria-pressed="false"
class="hoverdown-trigger crayons-reaction pseudo-reaction crayons-tooltip__activator relative">
<span class="crayons-reaction__icon crayons-reaction__icon--inactive" style="width: 40px; height: 40px">
<%= image_tag "heart-plus.svg", aria_hidden: true, height: 24, width: 24 %>
</span>
<span class="crayons-reaction__icon crayons-reaction__icon--active" style="width: 40px; height: 40px">
<%= image_tag "heart-plus-active.svg", aria_hidden: true, height: 24, width: 24 %>
</span>
<span class="crayons-reaction_total_count" id="reaction_total_count">
<%= @article.public_reactions_count %>
</span>
<span data-testid="tooltip" class="crayons-tooltip__content">
<%= t("views.reactions.drawer_button") %>
</span>
</button>
<div class="reaction-drawer" aria-expanded="false">
<div class="reaction-drawer__container">
<% ReactionCategory.for_view.each do |reaction_type| %>
<%= render partial: "articles/multireaction_button",
locals: {
category: reaction_type.slug.to_sym,
description: reaction_type.name,
image_path: "#{reaction_type.icon}.svg",
image_active_path: "#{reaction_type.icon}.svg",
aria_label: reaction_type.name
} %>
<% end %>
</div>
</div>
</div>
<button
id="reaction-butt-comment"
aria-label="<%= t("views.articles.jump_to_comments.aria_label") %>"
aria-pressed="false"
class="crayons-reaction crayons-reaction--comment crayons-tooltip__activator relative"
data-category="comment">
<span class="crayons-reaction__icon crayons-reaction__icon--inactive">
<%= crayons_icon_tag("comment.svg", aria_hidden: true) %>
</span>
<span class="crayons-reaction__count" id="reaction-number-comment"><%= @article.comments_count %></span>
<span data-testid="tooltip" class="crayons-tooltip__content">
<%= t("views.articles.jump_to_comments.label") %>
</span>
</button>
<%= render partial: "articles/reaction_button",
locals: {
category: :readinglist,
description: t("views.reactions.readingList.title"),
image_path: "save.svg",
image_active_path: "save-filled.svg",
aria_label: t("views.reactions.readingList.aria_label")
} %>

View file

@ -0,0 +1,23 @@
<button
id="reaction-butt-<%= category %>"
aria-label="<%= aria_label %>"
aria-pressed="false"
class="crayons-reaction crayons-tooltip__activator relative"
data-category="<%= category %>">
<span class="crayons-reaction__icon crayons-reaction__icon--inactive">
<%= crayons_icon_tag(image_path, native: true, aria_hidden: true, force_unique: true) %>
<%#= image_tag image_path, aria_hidden: true, height: 24, width: 24 %>
</span>
<%# TODO: This can be straightened out after feature-flag -- double icons are not required now %>
<% if user_signed_in? # We cannot trigger the action state unless the user is signed in, so no need to render. %>
<span class="crayons-reaction__icon crayons-reaction__icon--active">
<%= crayons_icon_tag(image_active_path, native: true, aria_hidden: true, force_unique: true) %>
<%#= image_tag image_active_path, aria_hidden: true, height: 24, width: 24 %>
</span>
<% end %>
<span class="crayons-reaction__count" id="reaction-number-<%= category %>"><span class="bg-base-40 opacity-25 p-2 inline-block radius-default"></span></span>
<span data-testid="tooltip" class="crayons-tooltip__content">
<%= description %>
</span>
</button>

View file

@ -0,0 +1,44 @@
<%= render partial: "articles/reaction_button",
locals: {
category: :like,
description: t("views.reactions.like.title"),
image_path: "heart.svg",
image_active_path: "heart-filled.svg",
aria_label: t("views.reactions.like.aria_label")
} %>
<% if FeatureFlag.enabled?(:replace_unicorn_with_jump_to_comments) %>
<button
id="reaction-butt-comment"
aria-label="<%= t("views.articles.jump_to_comments.aria_label") %>"
aria-pressed="false"
class="crayons-reaction crayons-reaction--comment crayons-tooltip__activator relative"
data-category="comment">
<span class="crayons-reaction__icon crayons-reaction__icon--inactive">
<%= crayons_icon_tag("comment.svg", aria_hidden: true) %>
</span>
<span class="crayons-reaction__count" id="reaction-number-comment"><%= @article.comments_count %></span>
<span data-testid="tooltip" class="crayons-tooltip__content">
<%= t("views.articles.jump_to_comments.label") %>
</span>
</button>
<% else %>
<%= render partial: "articles/reaction_button",
locals: {
category: :unicorn,
description: t("views.reactions.unicorn.title"),
image_path: "unicorn.svg",
image_active_path: "unicorn-filled.svg",
aria_label: t("views.reactions.unicorn.aria_label")
} %>
<% end %>
<%= render partial: "articles/reaction_button",
locals: {
category: :readinglist,
description: t("views.reactions.readingList.title"),
image_path: "save.svg",
image_active_path: "save-filled.svg",
aria_label: t("views.reactions.readingList.aria_label")
} %>

View file

@ -4,7 +4,6 @@
<%= render partial: "shared/auth_widget", locals: { tracking_id: "ca_left_sidebar_home_page", source: "left_sidebar" } unless user_signed_in? %>
<%= render partial: "layouts/main_nav", locals: { context: "sidebar" } %>
<%= render "layouts/sidebar_tags" %>
<%# the pattern .present?/.each has the advantage of issuing only a single SQL query to load objects in memory %>
<% end %>
<% cache("display-area-left-#{rand(5)}-#{user_signed_in?}", expires_in: 5.minutes) do %>
<% @left_sidebar_ad = DisplayAd.for_display("sidebar_left", user_signed_in?) %>

View file

@ -120,13 +120,28 @@
<div class="crayons-story__bottom">
<div class="crayons-story__details">
<% if story.public_reactions_count > 0 %>
<a href="<%= story.path %>" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left" data-reaction-count data-reactable-id="<%= story.id %>" aria-label="<%= t("views.articles.comments.aria_label", title: story.title) %>">
<%= crayons_icon_tag("small-heart", title: t("views.reactions.summary.title")) %>
<%= t("views.reactions.summary.count_html",
count: story.public_reactions_count,
start: tag("span", { class: %w[hidden s:inline] }, true),
end: "</span>".html_safe) %>
</a>
<% if FeatureFlag.enabled?(:multiple_reactions) %>
<div class="multiple_reactions_aggregate">
<% ReactionCategory.for_view.each do |reaction_type| %>
<% next unless story.reaction_categories.include?(reaction_type.slug.to_s) %>
<span class="crayons_icon_container">
<%= crayons_icon_tag(reaction_type.icon, width: "18px", height: "18px", native: true, aria_hidden: true) %>
</span>
<% end %>
<span class="aggregate_reactions_counter"><%= t("views.reactions.summary.count_html",
count: story.public_reactions_count,
start: tag("span", { class: %w[hidden s:inline] }, true),
end: "</span>".html_safe) %></span>
</div>
<% else %>
<a href="<%= story.path %>" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left" data-reaction-count data-reactable-id="<%= story.id %>" aria-label="<%= t("views.articles.comments.aria_label", title: story.title) %>">
<%= crayons_icon_tag("small-heart", title: t("views.reactions.summary.title")) %>
<%= t("views.reactions.summary.count_html",
count: story.public_reactions_count,
start: tag("span", { class: %w[hidden s:inline] }, true),
end: "</span>".html_safe) %>
</a>
<% end %>
<% end %>
<% if story.comments_count > 0 %>
<a href="<%= story.path %>#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left" aria-label="<%= t("views.articles.comments.aria_label", title: story.title) %>">

View file

@ -150,6 +150,8 @@
</div>
</div>
<%= render "articles/multiple_engagements" if FeatureFlag.enabled?(:multiple_reactions) && @article.public_reactions_count? %>
<h1 class="fs-3xl m:fs-4xl l:fs-5xl fw-bold s:fw-heavy lh-tight mb-2 <%= @article.title_length_classification %>">
<% if @article.search_optimized_title_preamble.present? && !user_signed_in? %>
<span class="fs-xl color-base-70 block"><%= @article.search_optimized_title_preamble %></span>

View file

@ -0,0 +1,7 @@
require "unique_svg_transform"
InlineSvg.configure do |config|
# NOTE: There is currently a bug in the inline_svg gem where custom attributes
# are de-facto required in order for a custom transformation to be activated.
config.add_custom_transformation attribute: :force_unique, transform: UniqueSvgTransform
end

View file

@ -11,6 +11,10 @@ en:
thumbsdown: Thumbs down
unicorn: Unicorn
vomit: Flag
lightbulb: Lightbulb
raised_hands: Raised hands
rocket: Rocket
drawer_button: Add reaction
summary:
count_html:
one: "%{count}%{start}\_reaction%{end}"
@ -29,3 +33,6 @@ en:
aria_label: Add to reading list
icon: Save button
text: Save
readinglist:
title: Save
aria_label: Add to reading list

View file

@ -11,6 +11,10 @@ fr:
thumbsdown: Thumbs down
unicorn: Unicorn
vomit: Flag
lightbulb: Lightbulb
raised_hands: Raised hands
rocket: Rocket
drawer_button: Add reaction
summary:
count_html:
one: "%{count}%{start}\_réaction%{end}"
@ -29,3 +33,8 @@ fr:
aria_label: Add to reading list
icon: Save button
text: Save
readinglist:
title: Sauver
aria_label: Add to reading list
icon: Save button
text: Save

View file

@ -1,13 +1,25 @@
---
like:
position: 1
icon: later
readinglist:
position: 2
icon: later
name: Like
icon: sparkle-heart
unicorn:
position: 2
name: Unicorn
icon: multi-unicorn
exploding-head:
position: 3
icon: later
icon: exploding-head
raised_hands:
position: 4
icon: raised-hands
fire:
position: 5
icon: fire
readinglist:
position: 5
icon: save
published: false
thumbsup:
privileged: true
score: 5.0
@ -16,6 +28,7 @@ thumbsdown:
score: -10.0
vomit:
privileged: true
name: Vomit
score: -50.0
thinking:
published: false

View file

@ -0,0 +1,7 @@
module DataUpdateScripts
class AddMultipleReactionsFeatureFlag
def run
FeatureFlag.add :multiple_reactions
end
end
end

View file

@ -0,0 +1,35 @@
require "securerandom"
# rubocop:disable Style/StringLiterals
class UniqueSvgTransform < InlineSvg::CustomTransformation
def transform(doc, uniq_str = SecureRandom.hex[0..6])
# nodes with id attributes
doc.xpath('//*[@id]').each do |node|
node_id = node.get_attribute 'id'
node.set_attribute 'id', [node_id, uniq_str].join('-')
end
# nodes with id references
doc.xpath('//*[@href]').each do |node|
node_href = node.get_attribute 'href'
node.set_attribute 'href', [node_href, uniq_str].join('-')
end
# nodes with fill references
doc.xpath('//*[@fill]').each do |node|
node_url = node.get_attribute 'fill'
if (md = /url\((.+)\)/.match node_url)
id = md[1]
end
next unless id
new_url = "url(#{id}-#{uniq_str})"
node.set_attribute 'fill', new_url
end
doc
end
end
# rubocop:enable Style/StringLiterals

61
spec/fixtures/files/unicorn-unique.svg vendored Normal file
View file

@ -0,0 +1,61 @@
<?xml version="1.0"?>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
<path d="M13.178 11.2a1.125 1.125 0 1 1 0-2.25 1.125 1.125 0 0 1 0 2.25Z" fill="url(#a-87a63a)"/>
<path d="M17.518 17.126c-.07.384.106.737.391.788.286.052.574-.216.644-.6.07-.385-.106-.738-.391-.79-.286-.052-.574.217-.644.602Z" fill="url(#b-87a63a)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="m18.394 14.92-.214-.461c-1.296.094-2.863.67-3.127 1.992-.112.557-.469 1.028-.924 1.413.028.06.053.117.078.174.113.256.218.492.5.788 1.334 1.4 3.325.74 4.093-.619.62-1.098.196-2.003-.406-3.288Zm-.485 2.996c-.285-.052-.46-.405-.39-.789.069-.385.357-.653.643-.602.285.052.46.405.391.79-.07.383-.358.652-.644.6Z" fill="url(#c-87a63a)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="m14.475 6.186.002.001c-.298.265-.288.796.038 1.213.322.412.825.556 1.155.345l-.005.01.027-.023a.112.112 0 0 1 .01-.006.081.081 0 0 0 .014-.008.022.022 0 0 0 .006-.007.027.027 0 0 1 .005-.007l.316-.256.006-.003c.178.195.335.395.46.598h.002c.648 1.22.82 2.491.98 3.66.12.893.234 1.727.545 2.433a37.68 37.68 0 0 0 .144.322c-1.296.094-2.863.67-3.127 1.991-.112.558-.469 1.029-.924 1.414-.17-.364-.433-.795-1.25-1.49-1.588-.317-2.563-1.045-2.563-2.552 0 0-.464-.223-1.065 3.505-.156.965-.098 1.93-.036 2.934l.017.294c.063 1.057-.796 1.895-1.855 1.895H3.521a1.969 1.969 0 0 1-1.969-1.969v-7.45 3.902c.464-1.023 1-2.468 1.464-3.714.371-.998.694-1.868.894-2.29 1.643-3.476 4.557-6.5 9.001-5.482a7.492 7.492 0 0 1 1.565.739Zm-1.297 5.012a1.125 1.125 0 1 1 0-2.25 1.125 1.125 0 0 1 0 2.25Z" fill="url(#d-87a63a)"/>
<path d="m16.686 4.673.034-.064.031-.057c.422-.782-.161-.511-.532-.255-.178.124-.855.382-1.522.621.24.148.49.35.75.57.35-.237.775-.516 1.239-.815Z" fill="url(#e-87a63a)"/>
<path d="M13.878 3.958c.292-.127.524-.227.63-.374.678-.928.487.27.415.617a3.887 3.887 0 0 1-.225.717 5.45 5.45 0 0 1-.597 1.05 7.02 7.02 0 0 0-1.19-.522 8.125 8.125 0 0 0-.804-.143c.534-.81 1.249-1.12 1.771-1.345Z" fill="url(#f-87a63a)"/>
<path d="M16.049 7.444c.178.195.335.396.46.599 0 0 1.298.328 1.971-.786.067-.11-.043-.236-.17-.222-.458.052-.9-.097-1.322-.344-.5.406-.852.687-.94.753Z" fill="url(#g-87a63a)"/>
<path d="M15.447 5.487c-.469.316-.805.554-.93.66-.003.003-.006.004-.01.006l-.009.005-.005.007c-.002.003-.003.006-.006.008l-.009.008-.002.003a8.712 8.712 0 0 0-.375-.217 5.44 5.44 0 0 0 .596-1.049c.24.147.49.349.75.57Z" fill="url(#h-87a63a)"/>
<path d="M13.263 4.25a3.192 3.192 0 0 0-1.156 1.053c-3.995-.5-6.655 2.363-8.197 5.625-.2.422-.523 1.292-.894 2.29-.463 1.245-1 2.69-1.463 3.714V9.779c0-1.26.24-2.596 1.032-3.577a5.7 5.7 0 0 1 .96-.933c.117-.09.282.035.244.178-.213.786-.317 1.869-.195 2.64.087-2.288 1.86-3.839 2.552-4.196 1.208-.623 2.4-.58 3.715-.471.113.01.184.142.146.25-.06.171-.11.413-.081.673.28-.51.935-.828 1.36-.741.77.156 1.45.38 1.977.648Z" fill="url(#i-87a63a)"/>
<path d="M13.646 5.738c.152-.238.27-.483.37-.693.118-.247.214-.446.316-.532.202-.168-.068-.17-.068-.17-.44.265-.972.662-1.278 1.129.21.07.432.16.66.266Z" fill="url(#j-87a63a)"/>
<path d="M14.475 6.187h.002c-.298.266-.288.796.038 1.213.322.412.825.556 1.155.346l-.005.01.027-.023a.115.115 0 0 1 .01-.006.081.081 0 0 0 .014-.008.022.022 0 0 0 .006-.007.027.027 0 0 1 .005-.007l.316-.256.006-.004c.11-.084.654-.517 1.38-1.113a2.647 2.647 0 0 1-.634-.613 2.576 2.576 0 0 1-.428-.838l.528-.342c.014.173.1.473.368.831.24.323.479.494.642.57.35-.29.728-.605 1.11-.929a2.775 2.775 0 0 1-.518-.531 2.71 2.71 0 0 1-.385-.707c.17-.106.34-.21.511-.314.044.174.144.408.342.672.18.243.357.397.503.492.36-.309.717-.62 1.05-.92a2.92 2.92 0 0 1-.694-.942l-.005-.014c.176-.101.347-.199.512-.291l.01.027.014.037a2.386 2.386 0 0 0 .566.764l.017.014a.21.21 0 0 1 .015.011c.952-.877 1.619-1.586 1.472-1.733-.417-.416-7.235 4.002-7.909 4.573l-.008.005-.01.006-.005.007-.006.007-.009.008-.003.005Z" fill="url(#k-87a63a)"/>
<path d="m19.824 2.761-.005-.014c.176-.102.347-.2.512-.292.01.022.013.04.024.064a2.357 2.357 0 0 0 .566.765l.033.025-.435.394a2.92 2.92 0 0 1-.695-.942ZM18.497 4.48a2.71 2.71 0 0 1-.385-.707l.511-.314c.044.174.144.408.342.672.18.244.358.398.504.493l-.454.388a2.775 2.775 0 0 1-.518-.532Zm-1.702 1.24a2.576 2.576 0 0 1-.428-.838l.528-.342c.014.172.1.472.368.831.24.323.48.494.642.57l-.475.392a2.646 2.646 0 0 1-.635-.613Z" fill="#EF5E3E"/>
<defs>
<linearGradient id="a-87a63a" x1="13.178" y1="8.949" x2="13.178" y2="11.199" gradientUnits="userSpaceOnUse">
<stop stop-color="#3A6481"/>
<stop offset="1" stop-color="#1E445E"/>
</linearGradient>
<linearGradient id="b-87a63a" x1="18.036" y1="16.518" x2="18.036" y2="17.921" gradientUnits="userSpaceOnUse">
<stop stop-color="#061722"/>
<stop offset="1" stop-color="#051622"/>
</linearGradient>
<linearGradient id="c-87a63a" x1="16.617" y1="14.459" x2="16.617" y2="19.605" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="d-87a63a" x1="10.329" y1="4.035" x2="10.329" y2="22.449" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3F3FF"/>
<stop offset="1" stop-color="#749CC4"/>
</linearGradient>
<linearGradient id="e-87a63a" x1="10.329" y1="4.035" x2="10.329" y2="22.449" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3F3FF"/>
<stop offset="1" stop-color="#749CC4"/>
</linearGradient>
<linearGradient id="f-87a63a" x1="13.322" y1="3.238" x2="13.322" y2="7.148" gradientUnits="userSpaceOnUse">
<stop stop-color="#DBEBF7"/>
<stop offset="1" stop-color="#9AB5C8"/>
</linearGradient>
<linearGradient id="g-87a63a" x1="10.09" y1="3.355" x2="10.09" y2="16.931" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="h-87a63a" x1="10.09" y1="3.355" x2="10.09" y2="16.931" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="i-87a63a" x1="10.09" y1="3.355" x2="10.09" y2="16.932" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="j-87a63a" x1="13.581" y1="4.344" x2="13.581" y2="6.288" gradientUnits="userSpaceOnUse">
<stop stop-color="#3A6481"/>
<stop offset="1" stop-color="#1E445E"/>
</linearGradient>
<linearGradient id="k-87a63a" x1="18.353" y1="1.549" x2="18.353" y2="7.842" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA271"/>
<stop offset="1" stop-color="#F36C24"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.8 KiB

60
spec/fixtures/files/unicorn.svg vendored Normal file
View file

@ -0,0 +1,60 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M13.178 11.2a1.125 1.125 0 1 1 0-2.25 1.125 1.125 0 0 1 0 2.25Z" fill="url(#a)"/>
<path d="M17.518 17.126c-.07.384.106.737.391.788.286.052.574-.216.644-.6.07-.385-.106-.738-.391-.79-.286-.052-.574.217-.644.602Z" fill="url(#b)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="m18.394 14.92-.214-.461c-1.296.094-2.863.67-3.127 1.992-.112.557-.469 1.028-.924 1.413.028.06.053.117.078.174.113.256.218.492.5.788 1.334 1.4 3.325.74 4.093-.619.62-1.098.196-2.003-.406-3.288Zm-.485 2.996c-.285-.052-.46-.405-.39-.789.069-.385.357-.653.643-.602.285.052.46.405.391.79-.07.383-.358.652-.644.6Z" fill="url(#c)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="m14.475 6.186.002.001c-.298.265-.288.796.038 1.213.322.412.825.556 1.155.345l-.005.01.027-.023a.112.112 0 0 1 .01-.006.081.081 0 0 0 .014-.008.022.022 0 0 0 .006-.007.027.027 0 0 1 .005-.007l.316-.256.006-.003c.178.195.335.395.46.598h.002c.648 1.22.82 2.491.98 3.66.12.893.234 1.727.545 2.433a37.68 37.68 0 0 0 .144.322c-1.296.094-2.863.67-3.127 1.991-.112.558-.469 1.029-.924 1.414-.17-.364-.433-.795-1.25-1.49-1.588-.317-2.563-1.045-2.563-2.552 0 0-.464-.223-1.065 3.505-.156.965-.098 1.93-.036 2.934l.017.294c.063 1.057-.796 1.895-1.855 1.895H3.521a1.969 1.969 0 0 1-1.969-1.969v-7.45 3.902c.464-1.023 1-2.468 1.464-3.714.371-.998.694-1.868.894-2.29 1.643-3.476 4.557-6.5 9.001-5.482a7.492 7.492 0 0 1 1.565.739Zm-1.297 5.012a1.125 1.125 0 1 1 0-2.25 1.125 1.125 0 0 1 0 2.25Z" fill="url(#d)"/>
<path d="m16.686 4.673.034-.064.031-.057c.422-.782-.161-.511-.532-.255-.178.124-.855.382-1.522.621.24.148.49.35.75.57.35-.237.775-.516 1.239-.815Z" fill="url(#e)"/>
<path d="M13.878 3.958c.292-.127.524-.227.63-.374.678-.928.487.27.415.617a3.887 3.887 0 0 1-.225.717 5.45 5.45 0 0 1-.597 1.05 7.02 7.02 0 0 0-1.19-.522 8.125 8.125 0 0 0-.804-.143c.534-.81 1.249-1.12 1.771-1.345Z" fill="url(#f)"/>
<path d="M16.049 7.444c.178.195.335.396.46.599 0 0 1.298.328 1.971-.786.067-.11-.043-.236-.17-.222-.458.052-.9-.097-1.322-.344-.5.406-.852.687-.94.753Z" fill="url(#g)"/>
<path d="M15.447 5.487c-.469.316-.805.554-.93.66-.003.003-.006.004-.01.006l-.009.005-.005.007c-.002.003-.003.006-.006.008l-.009.008-.002.003a8.712 8.712 0 0 0-.375-.217 5.44 5.44 0 0 0 .596-1.049c.24.147.49.349.75.57Z" fill="url(#h)"/>
<path d="M13.263 4.25a3.192 3.192 0 0 0-1.156 1.053c-3.995-.5-6.655 2.363-8.197 5.625-.2.422-.523 1.292-.894 2.29-.463 1.245-1 2.69-1.463 3.714V9.779c0-1.26.24-2.596 1.032-3.577a5.7 5.7 0 0 1 .96-.933c.117-.09.282.035.244.178-.213.786-.317 1.869-.195 2.64.087-2.288 1.86-3.839 2.552-4.196 1.208-.623 2.4-.58 3.715-.471.113.01.184.142.146.25-.06.171-.11.413-.081.673.28-.51.935-.828 1.36-.741.77.156 1.45.38 1.977.648Z" fill="url(#i)"/>
<path d="M13.646 5.738c.152-.238.27-.483.37-.693.118-.247.214-.446.316-.532.202-.168-.068-.17-.068-.17-.44.265-.972.662-1.278 1.129.21.07.432.16.66.266Z" fill="url(#j)"/>
<path d="M14.475 6.187h.002c-.298.266-.288.796.038 1.213.322.412.825.556 1.155.346l-.005.01.027-.023a.115.115 0 0 1 .01-.006.081.081 0 0 0 .014-.008.022.022 0 0 0 .006-.007.027.027 0 0 1 .005-.007l.316-.256.006-.004c.11-.084.654-.517 1.38-1.113a2.647 2.647 0 0 1-.634-.613 2.576 2.576 0 0 1-.428-.838l.528-.342c.014.173.1.473.368.831.24.323.479.494.642.57.35-.29.728-.605 1.11-.929a2.775 2.775 0 0 1-.518-.531 2.71 2.71 0 0 1-.385-.707c.17-.106.34-.21.511-.314.044.174.144.408.342.672.18.243.357.397.503.492.36-.309.717-.62 1.05-.92a2.92 2.92 0 0 1-.694-.942l-.005-.014c.176-.101.347-.199.512-.291l.01.027.014.037a2.386 2.386 0 0 0 .566.764l.017.014a.21.21 0 0 1 .015.011c.952-.877 1.619-1.586 1.472-1.733-.417-.416-7.235 4.002-7.909 4.573l-.008.005-.01.006-.005.007-.006.007-.009.008-.003.005Z" fill="url(#k)"/>
<path d="m19.824 2.761-.005-.014c.176-.102.347-.2.512-.292.01.022.013.04.024.064a2.357 2.357 0 0 0 .566.765l.033.025-.435.394a2.92 2.92 0 0 1-.695-.942ZM18.497 4.48a2.71 2.71 0 0 1-.385-.707l.511-.314c.044.174.144.408.342.672.18.244.358.398.504.493l-.454.388a2.775 2.775 0 0 1-.518-.532Zm-1.702 1.24a2.576 2.576 0 0 1-.428-.838l.528-.342c.014.172.1.472.368.831.24.323.48.494.642.57l-.475.392a2.646 2.646 0 0 1-.635-.613Z" fill="#EF5E3E"/>
<defs>
<linearGradient id="a" x1="13.178" y1="8.949" x2="13.178" y2="11.199" gradientUnits="userSpaceOnUse">
<stop stop-color="#3A6481"/>
<stop offset="1" stop-color="#1E445E"/>
</linearGradient>
<linearGradient id="b" x1="18.036" y1="16.518" x2="18.036" y2="17.921" gradientUnits="userSpaceOnUse">
<stop stop-color="#061722"/>
<stop offset="1" stop-color="#051622"/>
</linearGradient>
<linearGradient id="c" x1="16.617" y1="14.459" x2="16.617" y2="19.605" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="d" x1="10.329" y1="4.035" x2="10.329" y2="22.449" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3F3FF"/>
<stop offset="1" stop-color="#749CC4"/>
</linearGradient>
<linearGradient id="e" x1="10.329" y1="4.035" x2="10.329" y2="22.449" gradientUnits="userSpaceOnUse">
<stop stop-color="#E3F3FF"/>
<stop offset="1" stop-color="#749CC4"/>
</linearGradient>
<linearGradient id="f" x1="13.322" y1="3.238" x2="13.322" y2="7.148" gradientUnits="userSpaceOnUse">
<stop stop-color="#DBEBF7"/>
<stop offset="1" stop-color="#9AB5C8"/>
</linearGradient>
<linearGradient id="g" x1="10.09" y1="3.355" x2="10.09" y2="16.931" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="h" x1="10.09" y1="3.355" x2="10.09" y2="16.931" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="i" x1="10.09" y1="3.355" x2="10.09" y2="16.932" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F46E5"/>
<stop offset="1" stop-color="#60379A"/>
</linearGradient>
<linearGradient id="j" x1="13.581" y1="4.344" x2="13.581" y2="6.288" gradientUnits="userSpaceOnUse">
<stop stop-color="#3A6481"/>
<stop offset="1" stop-color="#1E445E"/>
</linearGradient>
<linearGradient id="k" x1="18.353" y1="1.549" x2="18.353" y2="7.842" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFA271"/>
<stop offset="1" stop-color="#F36C24"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

View file

@ -0,0 +1,15 @@
require "rails_helper"
require Rails.root.join(
"lib/data_update_scripts/20221220131148_add_multiple_reactions_feature_flag.rb",
)
describe DataUpdateScripts::AddMultipleReactionsFeatureFlag do
before do
allow(FeatureFlag).to receive(:add)
end
it "adds the feature flag" do
described_class.new.run
expect(FeatureFlag).to have_received(:add).with(:multiple_reactions)
end
end

View file

@ -0,0 +1,15 @@
require "rails_helper"
require "unique_svg_transform"
RSpec.describe UniqueSvgTransform do
let(:input) { "#{fixture_path}/files/unicorn.svg" }
let(:expectation) { File.read("#{fixture_path}/files/unicorn-unique.svg") }
let(:pseudo_unique) { "87a63a" }
it "transforms input doc into uniquely identified SVG" do
doc = Nokogiri::XML(File.open(input))
output = described_class.new(true).transform(doc, pseudo_unique).to_xml
expect(output).to eq expectation
end
end

View file

@ -1341,4 +1341,20 @@ RSpec.describe Article do
expect(another_article.errors.messages[:feed_source_url]).to include(error_message)
end
end
describe "#reaction_categories reports unique associated reaction categories" do
before do
user2 = create(:user)
user2.add_role(:trusted)
create(:reaction, reactable: article, category: "like")
create(:reaction, reactable: article, category: "like")
create(:reaction, reactable: article, category: "readinglist")
create(:reaction, reactable: article, category: "vomit", user: user2)
end
it "reports accurately" do
expect(article.reaction_categories).to contain_exactly("like", "readinglist", "vomit")
end
end
end

View file

@ -1,6 +1,6 @@
require "rails_helper"
RSpec.describe ReactionCategory, type: :model do
RSpec.describe ReactionCategory do
let(:attributes_hash) do
{
"slug" => "lol",
@ -17,12 +17,13 @@ RSpec.describe ReactionCategory, type: :model do
end
it "lists all category slugs" do
expect(described_class.all_slugs).to contain_exactly(*%i[like unicorn readinglist hands thinking thumbsup
thumbsdown vomit])
expect(described_class.all_slugs).to contain_exactly(*%i[exploding-head fire hands like
raised_hands readinglist thinking
thumbsdown thumbsup unicorn vomit])
end
it "lists public categories" do
expect(described_class.public).to contain_exactly(*%i[like readinglist unicorn])
expect(described_class.public).to contain_exactly(*%i[like unicorn raised_hands fire exploding-head])
end
it "lists privileged categories" do

View file

@ -139,8 +139,8 @@ RSpec.describe Reaction do
expected_result = [
{ category: "like", count: 1 },
{ category: "readinglist", count: 0 },
{ category: "unicorn", count: 1 },
{ category: "readinglist", count: 0 },
]
expect(described_class.count_for_article(article.id)).to eq(expected_result)
end

View file

@ -215,7 +215,7 @@ RSpec.describe "NotificationsIndex" do
end
it "does group notifications that are on different days but have the same reactable" do
mock_heart_reaction_notifications(2, %w[unicorn like readinglist])
mock_heart_reaction_notifications(2, %w[unicorn like])
Notification.last.update(created_at: Notification.last.created_at - 1.day)
get "/notifications"
notifications = controller.instance_variable_get(:@notifications)
@ -223,8 +223,8 @@ RSpec.describe "NotificationsIndex" do
end
it "does not group notifications that are on the same day but have different reactables" do
mock_heart_reaction_notifications(1, %w[unicorn like readinglist], article1)
mock_heart_reaction_notifications(1, %w[unicorn like readinglist], article2)
mock_heart_reaction_notifications(1, %w[unicorn like], article1)
mock_heart_reaction_notifications(1, %w[unicorn like], article2)
get "/notifications"
notifications = controller.instance_variable_get(:@notifications)
expect(notifications.count).to eq 2
@ -294,7 +294,7 @@ RSpec.describe "NotificationsIndex" do
end
it "does group notifications that are on different days but have the same reactable" do
mock_heart_reaction_notifications(2, %w[unicorn like readinglist])
mock_heart_reaction_notifications(2, %w[unicorn like])
Notification.last.update(created_at: Notification.last.created_at - 1.day)
get notifications_path(filter: :org, org_id: organization.id)
@ -303,8 +303,8 @@ RSpec.describe "NotificationsIndex" do
end
it "does not group notifications that are on the same day but have different reactables" do
mock_heart_reaction_notifications(1, %w[unicorn like readinglist], article1)
mock_heart_reaction_notifications(1, %w[unicorn like readinglist], article2)
mock_heart_reaction_notifications(1, %w[unicorn like], article1)
mock_heart_reaction_notifications(1, %w[unicorn like], article2)
get notifications_path(filter: :org, org_id: organization.id)
notifications = controller.instance_variable_get(:@notifications)

View file

@ -29,7 +29,7 @@ RSpec.describe "Reactions" do
{ "category" => "readinglist", "count" => 0 },
{ "category" => "unicorn", "count" => 0 },
]
expect(result["article_reaction_counts"]).to eq(expected_reactions_counts)
expect(result["article_reaction_counts"]).to contain_exactly(*expected_reactions_counts)
expect(result["reactions"].to_json).to eq(user.reactions.where(reactable: article).to_json)
end
@ -61,7 +61,7 @@ RSpec.describe "Reactions" do
{ "category" => "readinglist", "count" => 0 },
{ "category" => "unicorn", "count" => 0 },
]
expect(result["article_reaction_counts"]).to eq(expected_reactions)
expect(result["article_reaction_counts"]).to contain_exactly(*expected_reactions)
expect(result["reactions"]).to be_empty
end