Added socials.
|
@ -43,3 +43,19 @@ body {
|
|||
.icon.icon-fansly {
|
||||
fill: #2699f6;
|
||||
}
|
||||
|
||||
.icon.icon-linktree {
|
||||
fill: #43e660;
|
||||
}
|
||||
|
||||
.icon.icon-pornhub {
|
||||
fill: #ff9000;
|
||||
}
|
||||
|
||||
.icon.icon-cashapp {
|
||||
fill: #00c853;
|
||||
}
|
||||
|
||||
.icon.icon-loyalfans {
|
||||
fill: #d90a16;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
class="app-icon"
|
||||
viewBox="0 0 64 64"
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
sodipodi:docname="cashapp.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="namedview2"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="4.2922526"
|
||||
inkscape:cx="-6.4068923"
|
||||
inkscape:cy="41.004111"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2" />
|
||||
<path
|
||||
id="path1"
|
||||
style="fill-rule:nonzero"
|
||||
d="M 22.300781 0 C 15.900781 0 12.699219 -0.000390625 9.1992188 1.0996094 A 13.6 13.6 0 0 0 1.0996094 9.1992188 C -0.000390625 12.659219 0 15.880781 0 22.300781 L 0 41.689453 C 0 48.099453 -0.000390625 51.300781 1.0996094 54.800781 A 13.6 13.6 0 0 0 9.1992188 62.900391 C 12.659219 64.000391 15.880781 64 22.300781 64 L 41.699219 64 C 48.099219 64 51.300781 64.000859 54.800781 62.880859 A 13.6 13.6 0 0 0 62.900391 54.779297 C 64.000391 51.319297 64 48.099688 64 41.679688 L 64 22.310547 C 64 15.900547 64.000391 12.699219 62.900391 9.1992188 A 13.6 13.6 0 0 0 54.800781 1.0996094 C 51.300781 -0.000390625 48.099219 0 41.699219 0 L 22.300781 0 z M 34.660156 10.009766 L 39.5 10.009766 C 40.33 10.009766 40.949297 10.789141 40.779297 11.619141 L 39.990234 15.419922 A 19.73 19.73 0 0 1 46.710938 19.259766 C 47.270938 19.799766 47.299531 20.699219 46.769531 21.199219 L 44.269531 23.800781 C 43.799531 24.300781 42.970703 24.300781 42.470703 23.800781 L 42.490234 23.820312 C 40.380234 21.920312 37.149062 20.529297 33.789062 20.529297 C 31.149062 20.529297 28.519531 21.490156 28.519531 23.910156 C 28.519531 26.400156 31.339609 27.229453 34.599609 28.439453 C 40.299609 30.409453 45 32.830312 45 38.570312 C 45 44.800312 40.27925 49.069844 32.53125 49.589844 L 31.832031 52.980469 A 1.32 1.32 0 0 1 30.53125 54.039062 L 25.681641 54 C 24.851641 53.99 24.242109 53.220625 24.412109 52.390625 L 25.152344 48.820312 C 22.120344 47.990313 19.459375 46.489922 17.359375 44.419922 A 1.36 1.36 0 0 1 17.359375 42.5 L 20.060547 39.800781 A 1.3 1.3 0 0 1 21.900391 39.800781 C 24.500391 42.410781 27.860547 43.480469 31.060547 43.480469 C 34.580547 43.480469 36.960938 42.019297 36.960938 39.529297 C 36.960937 37.109297 34.570547 36.47 30.060547 34.75 C 25.290547 33.04 20.779297 30.55 20.779297 24.75 C 20.779297 18.05 26.239687 14.779219 32.679688 14.449219 L 33.380859 11.070312 A 1.32 1.32 0 0 1 34.660156 10.009766 z " />
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
|
@ -0,0 +1,16 @@
|
|||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 494.1 100" style="enable-background:new 0 0 494.1 100;" xml:space="preserve">
|
||||
<path d="M0,10.7h14.2v74.5h39.3v13.1H0V10.7z M67.5,10.7c4.8,0,8.9,3.7,8.9,8.6c0,4.9-4,8.8-8.9,8.8c-4.9,0-8.9-3.9-8.9-8.8
|
||||
C58.6,14.5,62.5,10.7,67.5,10.7z M60.5,35.2h13.6v63.2H60.5V35.2z M82.2,35.2h13.6v8.7c4-6.7,10.9-10.4,20.1-10.4
|
||||
c14.8,0,24,11.5,24,29.7v35.1h-13.6V64.5c0-11.8-5.2-18.5-14.5-18.5c-10.3,0-15.9,7-15.9,19.6v32.7H82.2L82.2,35.2L82.2,35.2z
|
||||
M147.1,10.7h13.6v55.4l25.4-30.9h17.1l-27.1,31.6l27.1,31.5h-17.1l-25.4-30.8v30.8h-13.6V10.7z M208.6,19.1h13.9v16.1h16.2v11.3
|
||||
h-16.2v32.5c0,4.1,2.5,6.7,6.5,6.7h9.1v12.7h-10.9c-11.8,0-18.5-7-18.5-19.4L208.6,19.1L208.6,19.1z M245.6,35.2h12.6V43
|
||||
c3.4-6,9-9.5,15.9-9.5c2.1,0,3.2,0.1,4.8,0.6v12.6c-0.9-0.2-2.3-0.5-5.1-0.5c-10,0-15.5,8.4-15.5,22.8v29.2h-13.6V35.2H245.6z
|
||||
M310.8,33.5c15,0,31.3,9,31.3,34.7V70h-48.8c1.1,11.3,7.6,17.5,18.6,17.5c7.9,0,14.5-4.2,16-10.1h13.9
|
||||
C340.3,90,327.1,100,311.8,100c-19.6,0-32-12.7-32-33.3C279.8,48.4,291.7,33.5,310.8,33.5z M327.5,58.8c-1.9-7.8-8.1-12.7-16.7-12.7
|
||||
c-8.3,0-14.2,5-16.5,12.7H327.5z M379.1,33.5c15,0,31.3,9,31.3,34.7V70h-48.8c1.1,11.3,7.6,17.5,18.6,17.5c7.9,0,14.5-4.2,16-10.1
|
||||
H410C408.6,90,395.4,100,380.1,100c-19.6,0-32-12.7-32-33.3C348.1,48.4,360,33.5,379.1,33.5z M395.8,58.8
|
||||
c-1.9-7.8-8.1-12.7-16.8-12.7c-8.3,0-14.2,5-16.5,12.7H395.8z M413.7,33.3H438l-17.3-16.4l9.5-9.7L446.7,24V0h14.3v24l16.5-16.8
|
||||
l9.5,9.7l-17.3,16.4h24.3v13.6h-24.5L487,63.7l-9.5,9.5l-23.7-23.7l-23.7,23.7l-9.5-9.5L438,46.8h-24.5V33.3H413.7z M446.8,66.2
|
||||
h14.3v32.2h-14.3V66.2z">
|
||||
</path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 98 98.000003"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="linktree.svg"
|
||||
width="98"
|
||||
height="98"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="3.7971541"
|
||||
inkscape:cx="-1.1850981"
|
||||
inkscape:cy="63.205231"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<path
|
||||
d="M 9.2,33.2 H 33.4 L 16.1,16.8 25.6,7.2 42,23.9 V 0.1 H 56.2 V 23.9 L 72.6,7.2 82.1,16.8 64.8,33.1 H 89 V 46.6 H 64.7 L 82,63.3 72.5,72.7 49,49.2 25.5,72.8 16,63.3 33.3,46.6 H 9 V 33.2 Z m 32.9,32.7 h 14.2 v 32 H 42.1 Z"
|
||||
id="path1">
|
||||
</path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 26.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 213.99999 214"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="loyalfans.svg"
|
||||
width="214"
|
||||
height="214"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.5023135"
|
||||
inkscape:cx="-47.593262"
|
||||
inkscape:cy="179.72281"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<path
|
||||
d="m 71.2,29.9 c 8.5,-8.5 22.5,-8.5 31,0 8.5,8.5 8.5,22.5 0,31 -8.5,8.5 -22.5,8.5 -31,0 -8.5,-8.5 -8.7,-22.5 0,-31 z M 212,0.4 133.9,97.3 c -1.2,1.4 -2.4,3 -3.6,4.4 -5.9,7.7 -11.1,16 -15.2,24.9 -8.3,17.6 -13.1,37.4 -13.1,58.3 v 28.7 H 71 v -30.1 c 0,-2.6 0,-5.1 -0.2,-7.7 -1,-18 -5.5,-34.8 -12.7,-50.2 -1.8,-3.8 -3.8,-7.5 -5.9,-11.3 L 51.4,112.9 2,30.5 l 84.8,72 v 0 0 z"
|
||||
id="path1" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" data-name="logo-default" data-testid="logo-default-icon" viewBox="0 -0.04 154.28 24.03">
|
||||
<path d="m110.215 23.07 7.873-17.456h-3.862l-4.534 9.913-4.557-9.913h-3.858l7.89 17.465zm13.452-17.506h-3.537V23.07h3.537zm3.33.05v17.434h1.417v.031h3.51a8.76 8.76 0 0 0 6.154-2.567 7.963 7.963 0 0 0 1.868-2.761 8.624 8.624 0 0 0 0-6.8 8.348 8.348 0 0 0-1.868-2.793 8.586 8.586 0 0 0-6.154-2.54zm3.483 13.978V9.101h1.444a5.245 5.245 0 0 1 0 10.49zm17.339-3.885.077.027c1.818.623 2.892 1.493 2.892 2.441a1.919 1.919 0 0 1-1.918 1.769 6.034 6.034 0 0 1-4.68-1.965l-2.54 2.391a9.681 9.681 0 0 0 4.155 2.639 10.322 10.322 0 0 0 3.068.424 5.418 5.418 0 0 0 5.405-5.247 5.053 5.053 0 0 0-1.367-3.415 9.316 9.316 0 0 0-3.858-2.319c-.149-.05-.3-.1-.5-.149-1.6-.4-2.22-.9-2.242-1.791a1.225 1.225 0 0 1 .3-1 2.664 2.664 0 0 1 1.543-.65 5.379 5.379 0 0 1 3.366 1.2l.618.4 1.818-2.964-.573-.374a8.663 8.663 0 0 0-5.234-1.742 6.071 6.071 0 0 0-4.061 1.715 4.67 4.67 0 0 0-1.272 3.56 4.815 4.815 0 0 0 2.238 3.939 8.459 8.459 0 0 0 2.639 1.1zM95.543 5.61l-3.56 6.574-3.587-6.574h-3.858l5.7 11.059v6.4h3.483v-6.4L99.405 5.61zm-17.484 0v9.764L68.422 5.61h-1.1v17.461h3.488V12.882l9.809 10.188h.92V5.609zm-21.991 0-8.893 17.451h3.912l1.642-3.24h7.224l1.521 3.244h3.858l-8.244-17.46zm.42 6.849 1.818 3.889h-3.785zM43.21 5.605l-7.273 6.619L28.645 5.6h-1.2v17.461h3.488V12.377l5 4.557 5.008-4.557v10.684h3.488V5.6H43.21zM10.63 19.118a1.876 1.876 0 0 0-.041-.217c-.239-.911-.465-1.823-.726-2.725a.569.569 0 0 1 .176-.677 1.3 1.3 0 0 0 .221-1.543 1.418 1.418 0 0 0-1.313-.772 1.376 1.376 0 0 0-1.232.938 1.3 1.3 0 0 0 .415 1.507.358.358 0 0 1 .117.438c-.257.916-.492 1.836-.735 2.757a2.781 2.781 0 0 0-.041.289h3.158m-1.6-7.9a5.417 5.417 0 0 1 1.642-2.134 4.357 4.357 0 0 1 5.577.352 5.785 5.785 0 0 1 1.187 6.736 13.445 13.445 0 0 1-2.725 3.61 28.84 28.84 0 0 1-5.491 4.16.424.424 0 0 1-.352.027 27.5 27.5 0 0 1-6.4-5.094 9.672 9.672 0 0 1-2.279-3.84 5.677 5.677 0 0 1 1.8-5.766 4.324 4.324 0 0 1 6.6 1.169c.149.239.28.492.438.781M5.503 2.722c.4.379.79.781 1.214 1.132.352.293.537.262.772-.126.329-.546.618-1.11.9-1.683.036-.077-.036-.244-.108-.329A1.015 1.015 0 0 1 8.387.222a1.006 1.006 0 0 1 1.363 1.48.31.31 0 0 0-.072.438c.3.532.573 1.087.893 1.611.221.361.37.37.722.126a3.189 3.189 0 0 0 .447-.37c.266-.266.519-.541.776-.808-.492-.672-.4-1.358.2-1.678a1.006 1.006 0 0 1 1.354.406.985.985 0 0 1-.424 1.367.619.619 0 0 0-.388.492c-.23.947-.483 1.886-.717 2.833a.486.486 0 0 1-.532.415q-3-.007-5.992 0a.48.48 0 0 1-.532-.411c-.244-.966-.505-1.931-.74-2.9a.513.513 0 0 0-.334-.411 1 1 0 0 1-.564-1.132 1.026 1.026 0 0 1 .862-.767.982.982 0 0 1 1.074.659c.14.361.063.672-.284 1.142"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
data-name="logo-default"
|
||||
data-testid="logo-default-icon"
|
||||
viewBox="0 -0.04 25 24.999999"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
width="25"
|
||||
height="25"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<path
|
||||
d="m 14.114527,19.614142 c -0.0094,-0.07308 -0.02308,-0.145535 -0.041,-0.217 -0.239,-0.911 -0.465,-1.823 -0.726,-2.725 -0.103352,-0.239339 -0.03083,-0.5183 0.176,-0.677 0.408042,-0.408238 0.498047,-1.036638 0.221,-1.543 -0.250584,-0.490502 -0.762542,-0.791516 -1.313,-0.772 -0.56432,0.03001 -1.052913,0.40201 -1.232,0.938 -0.212834,0.538692 -0.04361,1.15321 0.415,1.507 0.138253,0.09956 0.18719,0.28276 0.117,0.438 -0.257,0.916 -0.492,1.836 -0.735,2.757 -0.01872,0.09555 -0.0324,0.192015 -0.041,0.289 h 3.158 m -1.6,-7.9 c 0.359002,-0.838194 0.923783,-1.572203 1.642,-2.1340003 1.699386,-1.2472131 4.047914,-1.0989825 5.577,0.352 1.822799,1.7459463 2.303203,4.4721513 1.187,6.7360003 -0.697958,1.348581 -1.619326,2.569183 -2.725,3.61 -1.657551,1.601073 -3.501032,2.997701 -5.491,4.16 -0.107829,0.05974 -0.236321,0.0696 -0.352,0.027 -2.3753486,-1.369455 -4.5325397,-3.086444 -6.3999999,-5.094 -1.048787,-1.084717 -1.8292518,-2.39976 -2.2790001,-3.84 -0.5571739,-2.108189 0.1424143,-4.349203 1.8000001,-5.7660003 2.0738766,-1.7880035 5.2664089,-1.2225383 6.5999999,1.1690003 0.149,0.239 0.28,0.492 0.438,0.781 M 8.9875271,3.2181417 c 0.4,0.379 0.79,0.781 1.2139999,1.132 0.352,0.293 0.537,0.262 0.772,-0.126 0.329,-0.546 0.618,-1.11 0.9,-1.683 0.036,-0.077 -0.036,-0.244 -0.108,-0.329 -0.410242,-0.4325033 -0.361198,-1.1237515 0.106,-1.49399998 0.986666,-0.90866627 2.349666,0.57133368 1.363,1.47999998 -0.142839,0.099814 -0.17537,0.2977135 -0.072,0.438 0.3,0.532 0.573,1.087 0.893,1.611 0.221,0.361 0.37,0.37 0.722,0.126 0.159934,-0.109493 0.309554,-0.2333389 0.447,-0.37 0.266,-0.266 0.519,-0.541 0.776,-0.808 -0.492,-0.672 -0.4,-1.358 0.2,-1.678 0.486391,-0.2579836 1.089809,-0.077047 1.354,0.406 0.277315,0.4928925 0.08357,1.1175522 -0.424,1.367 -0.208917,0.081596 -0.357352,0.2698181 -0.388,0.492 -0.23,0.947 -0.483,1.886 -0.717,2.833 -0.03678,0.2589486 -0.271887,0.4423538 -0.532,0.415 -2,-0.00467 -3.997333,-0.00467 -5.9919999,0 -0.2596827,0.030788 -0.4962279,-0.1519569 -0.532,-0.411 -0.244,-0.966 -0.505,-1.931 -0.74,-2.9 -0.026543,-0.1883196 -0.15509,-0.3465019 -0.334,-0.411 -0.4330367,-0.1954461 -0.6687764,-0.6685975 -0.564,-1.132 0.1027353,-0.4083194 0.4445066,-0.7124244 0.862,-0.767 0.4703187,-0.069255 0.9227059,0.2083271 1.074,0.659 0.14,0.361 0.063,0.672 -0.284,1.142"
|
||||
id="path1" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.2"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 79.5 75"
|
||||
overflow="visible"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="pornhub.svg"
|
||||
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs3" /><sodipodi:namedview
|
||||
id="namedview3"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="10.906667"
|
||||
inkscape:cx="39.746333"
|
||||
inkscape:cy="37.5"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1020"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<path
|
||||
id="path1"
|
||||
d="M 64.099609 46 C 62.799609 46 61.599219 46.499609 60.699219 47.599609 C 59.799219 48.699609 59.300781 50.3 59.300781 52.5 C 59.300781 54.8 59.700391 56.4 60.400391 57.5 C 61.400391 59 62.700391 59.800781 64.400391 59.800781 C 65.600391 59.800781 66.699609 59.299219 67.599609 58.199219 C 68.499609 57.199219 68.9 55.4 69 53 C 69 50.5 68.499609 48.699609 67.599609 47.599609 C 66.699609 46.499609 65.499609 46 64.099609 46 z " /><path
|
||||
id="path4"
|
||||
d="M 3.6992188 25.300781 C 1.4992187 25.600781 0.2 26.900391 0 29.400391 L 0 70.800781 C 0.2 73.300781 1.3992187 74.700391 3.6992188 74.900391 L 75.900391 74.900391 C 78.100391 74.600391 79.399609 73.300781 79.599609 70.800781 L 79.599609 29.400391 C 79.299609 26.900391 78.100781 25.500781 75.800781 25.300781 L 3.6992188 25.300781 z M 7.0996094 33.900391 L 12.800781 33.900391 L 12.800781 43.699219 C 14.300781 42.299219 17.400781 41.599219 19.800781 41.699219 L 19.900391 41.699219 C 21.100391 41.699219 22.099609 42.000391 23.099609 42.400391 C 24.199609 42.900391 25 43.500781 25.5 44.300781 C 26 45.100781 26.400781 45.900391 26.800781 46.900391 C 27.000781 47.800391 27.099609 49.199219 27.099609 51.199219 L 27.099609 63.800781 L 21.300781 63.800781 L 21.300781 52.300781 C 21.300781 50.100781 21.2 48.6 21 48 C 20.7 47.4 20.400781 46.9 19.800781 46.5 C 19.200781 46.2 18.499219 46 17.699219 46 C 16.699219 46 15.899609 46.199219 15.099609 46.699219 C 14.299609 47.199219 13.700391 47.900781 13.400391 48.800781 C 13.100391 49.800781 12.900391 51.2 12.900391 53 L 12.900391 63.900391 L 7.0996094 63.900391 L 7.0996094 33.900391 z M 53.400391 33.900391 L 59.199219 33.900391 L 58.900391 43.599609 C 60.300391 42.399609 63.299219 41.499219 65.699219 41.699219 C 66.399219 41.799219 67 41.9 67.5 42 C 69.3 42.4 70.999219 43.199609 72.199219 44.599609 C 73.899219 46.499609 74.799609 49.199219 74.599609 52.699219 C 74.599609 56.399219 73.700391 59.300781 71.900391 61.300781 C 70.200391 63.300781 68 64.300781 65.5 64.300781 L 65.300781 64.300781 L 65 64.300781 C 62 64.300781 60.000781 63.199219 58.800781 62.199219 L 58.800781 63.900391 L 53.400391 63.900391 L 53.400391 33.900391 z M 30.300781 42.199219 L 36.099609 42.199219 L 36.099609 52.199219 C 36.099609 55.199219 36.200391 57.100781 36.400391 57.800781 C 36.700391 58.400781 37.099609 59.000391 37.599609 59.400391 C 38.099609 59.800391 38.799219 60 39.699219 60 C 40.599219 60 41.500781 59.699219 42.300781 59.199219 C 43.100781 58.699219 43.600391 57.999219 43.900391 57.199219 C 44.200391 56.399219 44.300781 54.400781 44.300781 51.300781 L 44.199219 42.199219 L 50 42.199219 L 50 64 L 44.699219 64 L 44.699219 61.599609 C 43.799219 62.699609 41.899219 64.4 38.199219 64.5 L 37.5 64.5 C 36.1 64.5 34.799609 64.1 33.599609 63.5 C 32.499609 62.9 31.599609 62.000781 31.099609 60.800781 C 30.599609 59.700781 30.300781 58 30.300781 56 L 30.300781 42.199219 z " />
|
||||
|
||||
<path
|
||||
d="M74.6,9.9c-0.2-0.7-0.5-1.3-0.9-1.9c-0.4-0.6-1-1-1.9-1.4c-0.8-0.4-1.7-0.5-2.7-0.5c-1.8-0.1-4.7,0.6-5.6,2V6.5h-4.2v17h4.5 v-7.7c0-1.9,0.1-3.2,0.3-3.9c0.2-0.7,0.7-1.3,1.3-1.7c0.6-0.4,1.3-0.6,2.1-0.6c0.6,0,1.1,0.2,1.6,0.4c0.4,0.3,0.7,0.7,0.9,1.3 c0.2,0.5,0.3,1.7,0.3,3.6v8.7h4.5V12.9C74.8,11.6,74.7,10.6,74.6,9.9 M53.1,6.2c-1.3,0.2-2.6,1.2-3.2,1.9V6.5h-4.2v17h4.5v-5.3 c0-2.9,0.1-4.8,0.4-5.7c0.3-0.9,0.6-1.5,1-1.9c0.4-0.3,1-0.5,1.6-0.5c0.7,0,1.4,0.2,2.1,0.7l1.4-3.9c-1-0.6-1.9-0.8-3-0.8 C53.6,6.1,53.3,6.1,53.1,6.2 M34.8,6.1c-1.7,0-3.2,0.4-4.5,1.1c-1.4,0.7-2.4,1.8-3.1,3.2c-0.7,1.4-1.1,2.8-1.1,4.3 c0,2,0.4,3.6,1.1,5c0.7,1.4,1.8,2.4,3.2,3.1c1.4,0.7,2.9,1.1,4.5,1.1c2.5,0,4.6-0.8,6.3-2.5c1.7-1.7,2.5-3.8,2.5-6.4 c0-2.6-0.8-4.7-2.5-6.3C39.4,6.9,37.3,6.1,34.8,6.1 M37.7,18.9c-0.8,0.9-1.8,1.3-3,1.3c-1.2,0-2.2-0.4-3-1.3 C31,18,30.6,16.7,30.6,15s0.4-3,1.2-3.9c0.8-0.9,1.8-1.3,3-1.3c1.2,0,2.2,0.4,3,1.3c0.8,0.9,1.2,2.2,1.2,3.8 C38.9,16.7,38.5,18,37.7,18.9 M20.3,0.4C19.4,0.1,17.5,0,14.6,0H7v23.5h4.7v-8.9h3.1c2.1,0,3.8-0.1,4.9-0.3c0.8-0.2,1.7-0.6,2.5-1.1 s1.5-1.3,2-2.3c0.5-1,0.8-2.2,0.8-3.6c0-1.9-0.5-3.4-1.4-4.6C22.8,1.5,21.6,0.7,20.3,0.4 M19.6,9.1c-0.4,0.5-0.9,0.9-1.5,1.2 s-1.9,0.4-3.7,0.4h-2.6V4h2.3c1.7,0,2.8,0.1,3.4,0.2c0.8,0.1,1.4,0.5,1.9,1c0.5,0.6,0.8,1.3,0.8,2.1C20.1,8,20,8.6,19.6,9.1"
|
||||
id="path3" />
|
||||
</svg>
|
After Width: | Height: | Size: 5.2 KiB |
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter-x" viewBox="0 0 16 16">
|
||||
<path d="M12.6.75h2.454l-5.36 6.142L16 15.25h-4.937l-3.867-5.07-4.425 5.07H.316l5.733-6.57L0 .75h5.063l3.495 4.633L12.601.75Zm-.86 13.028h1.36L4.323 2.145H2.865z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 301 B |
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="300.25"
|
||||
height="300.25"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<path
|
||||
d="M 178.695,127.15 290.395,0 h -26.46 L 166.905,110.38 89.465,0 H 0.125 L 117.255,166.93 0.125,300.25 h 26.46 l 102.4,-116.59 81.8,116.59 h 89.34 M 36.135,19.54 h 40.65 l 187.13,262.13 h -40.66"
|
||||
id="path1" />
|
||||
</svg>
|
After Width: | Height: | Size: 470 B |
|
@ -294,6 +294,44 @@
|
|||
>{{ actor.agency }}</span>
|
||||
</li>
|
||||
|
||||
<div class="bio-item bio-socials hideable">
|
||||
<ul class="socials">
|
||||
<a
|
||||
v-for="social in socials"
|
||||
:key="`social-${social.id}`"
|
||||
:href="getSocialUrl(social)"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
:title="social.platform || social.url"
|
||||
class="social ellipsis"
|
||||
>
|
||||
<Icon
|
||||
v-if="social.platform && env.socials.urls[social.platform]"
|
||||
:icon="iconMap[social.platform] || social.platform"
|
||||
:title="social.platform"
|
||||
:class="`icon-social icon-${social.platform}`"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-else-if="social.platform"
|
||||
icon="bubbles10"
|
||||
:title="social.platform"
|
||||
:class="`icon-social icon-${social.platform} icon-generic`"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-else-if="social.url"
|
||||
icon="sphere"
|
||||
:title="social.platform"
|
||||
:class="`icon-social icon-${social.platform} icon-generic`"
|
||||
/>
|
||||
|
||||
<template v-if="social.platform">{{ env.socials.prefix[social.platform] || env.socials.prefix.default }}</template>
|
||||
{{ social.handle }}
|
||||
</a>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<li class="bio-item updated">
|
||||
<span
|
||||
class="ellipsis"
|
||||
|
@ -372,6 +410,7 @@
|
|||
|
||||
<script setup>
|
||||
import { ref, inject } from 'vue';
|
||||
import formatTemplate from 'template-format';
|
||||
|
||||
import getPath from '#/src/get-path.js';
|
||||
import { formatDate } from '#/utils/format.js';
|
||||
|
@ -379,7 +418,7 @@ import { formatDate } from '#/utils/format.js';
|
|||
const expanded = ref(false);
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const user = pageContext.user;
|
||||
const { user, env } = pageContext;
|
||||
|
||||
const props = defineProps({
|
||||
actor: {
|
||||
|
@ -388,6 +427,10 @@ const props = defineProps({
|
|||
},
|
||||
});
|
||||
|
||||
const iconMap = {
|
||||
twitter: 'twitter-x',
|
||||
};
|
||||
|
||||
// if the profile is empty, the expand button overlaps the header
|
||||
const showExpand = [
|
||||
'age',
|
||||
|
@ -429,6 +472,25 @@ const descriptions = Object.values(Object.fromEntries(props.actor.profiles
|
|||
text: profile.description,
|
||||
entity: profile.entity,
|
||||
}])));
|
||||
|
||||
function getSocialUrl(social) {
|
||||
if (social.url) {
|
||||
return social.url;
|
||||
}
|
||||
|
||||
if (pageContext.env.socials.urls[social.platform]) {
|
||||
return formatTemplate(pageContext.env.socials.urls[social.platform], { handle: social.handle });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const socials = props.actor.socials.map((social) => ({
|
||||
...social,
|
||||
handle: social.url
|
||||
? new URL(social.url).hostname
|
||||
: social.handle,
|
||||
}));
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
@ -653,6 +715,49 @@ const descriptions = Object.values(Object.fromEntries(props.actor.profiles
|
|||
}
|
||||
}
|
||||
|
||||
.bio-socials {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.socials {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
|
||||
grid-gap: 0 0;
|
||||
overflow: hidden;
|
||||
gap: .25rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.social {
|
||||
display: flex;
|
||||
height: 2rem;
|
||||
align-items: center;
|
||||
padding: .1rem .5rem;
|
||||
border-radius: .25rem;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
font-size: .9rem;
|
||||
font-weight: normal;
|
||||
background: var(--highlight-weak-40);
|
||||
|
||||
.icon {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.icon-generic {
|
||||
fill: var(--highlight);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actor-actions {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
|
|
@ -0,0 +1,109 @@
|
|||
<template>
|
||||
<ul class="socials nolist">
|
||||
<li
|
||||
v-for="social in socials"
|
||||
:key="`social-${rev.id}-${index}-${social.id}`"
|
||||
class="delta-item"
|
||||
:class="{ modified: social.modified }"
|
||||
>
|
||||
<a
|
||||
:href="getUrl(social)"
|
||||
target="_blank"
|
||||
class="link"
|
||||
>
|
||||
<Icon
|
||||
v-if="social.platform && env.socials.urls[social.platform]"
|
||||
:icon="iconMap[social.platform] || social.platform"
|
||||
:title="social.platform"
|
||||
:class="`icon-social icon-${social.platform}`"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-else-if="social.platform"
|
||||
icon="bubbles10"
|
||||
:title="social.platform"
|
||||
:class="`icon-social icon-${social.platform} icon-generic`"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-else-if="social.url"
|
||||
icon="sphere"
|
||||
:title="social.platform"
|
||||
:class="`icon-social icon-${social.platform} icon-generic`"
|
||||
/>
|
||||
|
||||
{{ social.handle || social.url }}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { inject } from 'vue';
|
||||
|
||||
import formatTemplate from 'template-format';
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const { env } = pageContext;
|
||||
|
||||
defineProps({
|
||||
rev: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
socials: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
});
|
||||
|
||||
const iconMap = {
|
||||
twitter: 'twitter-x',
|
||||
};
|
||||
|
||||
function getUrl(social) {
|
||||
if (social.url) {
|
||||
return social.url;
|
||||
}
|
||||
|
||||
if (env.socials.urls[social.platform]) {
|
||||
return formatTemplate(env.socials.urls[social.platform], { handle: social.handle });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.socials {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: .5rem;
|
||||
}
|
||||
|
||||
.delta-item .link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: .25rem .5rem;
|
||||
border-radius: .25rem;
|
||||
box-shadow: 0 0 3px var(--shadow-weak-20);
|
||||
color: inherit;
|
||||
|
||||
.icon {
|
||||
margin-right: .5rem;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.icon-generic {
|
||||
fill: var(--glass-strong-10);
|
||||
}
|
||||
}
|
||||
|
||||
.delta-item.modified {
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
|
@ -104,8 +104,15 @@
|
|||
|
||||
<div class="delta-deltas">
|
||||
<span class="delta-from delta-value">
|
||||
<Socials
|
||||
v-if="delta.key === 'socials'"
|
||||
:rev="rev"
|
||||
:index="index"
|
||||
:socials="rev.base[delta.key]"
|
||||
/>
|
||||
|
||||
<ul
|
||||
v-if="Array.isArray(rev.base[delta.key])"
|
||||
v-else-if="Array.isArray(rev.base[delta.key])"
|
||||
class="nolist"
|
||||
>[
|
||||
<li
|
||||
|
@ -129,8 +136,15 @@
|
|||
<span class="delta-arrow">⇒</span>
|
||||
|
||||
<span class="delta-to delta-value">
|
||||
<Socials
|
||||
v-if="delta.key === 'socials'"
|
||||
:rev="rev"
|
||||
:index="index"
|
||||
:socials="delta.value"
|
||||
/>
|
||||
|
||||
<ul
|
||||
v-if="Array.isArray(delta.value)"
|
||||
v-else-if="Array.isArray(delta.value)"
|
||||
class="nolist"
|
||||
>[
|
||||
<li
|
||||
|
@ -218,6 +232,7 @@ import { ref, computed, inject } from 'vue';
|
|||
import { format } from 'date-fns';
|
||||
|
||||
import Avatar from '#/components/edit/avatar.vue';
|
||||
import Socials from '#/components/edit/revision-socials.vue';
|
||||
import Checkbox from '#/components/form/checkbox.vue';
|
||||
|
||||
import { get, post } from '#/src/api.js';
|
||||
|
@ -277,6 +292,14 @@ const curatedRevisions = computed(() => revisions.value.map((revision) => {
|
|||
}))];
|
||||
}
|
||||
|
||||
if (key === 'socials') {
|
||||
// new socials don't have IDs yet, so we need to compare the values
|
||||
return [key, value.map((item) => ({
|
||||
...item,
|
||||
modified: revision.deltas.some((delta) => delta.key === key && !delta.value.some((deltaItem) => deltaItem.url === item.url || `${deltaItem.platform}:${deltaItem.handle}` === `${item.platform}:${item.handle}`)),
|
||||
}))];
|
||||
}
|
||||
|
||||
if (dateKeys.includes(key)) {
|
||||
return [key, new Date(value)];
|
||||
}
|
||||
|
@ -300,6 +323,17 @@ const curatedRevisions = computed(() => revisions.value.map((revision) => {
|
|||
};
|
||||
}
|
||||
|
||||
if (delta.key === 'socials') {
|
||||
// new socials don't have IDs yet, so we need to compare the values
|
||||
return {
|
||||
...delta,
|
||||
value: delta.value.map((social) => ({
|
||||
...social,
|
||||
modified: !revision.base[delta.key].some((baseItem) => baseItem.url === social.url || `${baseItem.platform}:${baseItem.handle}` === `${social.platform}:${social.handle}`),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
if (dateKeys.includes(delta.key)) {
|
||||
return {
|
||||
...delta,
|
||||
|
|
|
@ -0,0 +1,289 @@
|
|||
<template>
|
||||
<ul
|
||||
class="list nolist"
|
||||
:class="{ disabled: !editing.has('socials') }"
|
||||
>
|
||||
<li
|
||||
v-for="(social, index) in socials"
|
||||
:key="`socials-${social}`"
|
||||
class="list-item"
|
||||
:class="{ deleted: !socials.some((listItem) => listItem.social === social.social || listItem.url === social.url) }"
|
||||
>
|
||||
<a
|
||||
:href="getUrl(social)"
|
||||
target="_blank"
|
||||
class="link"
|
||||
>
|
||||
<Icon
|
||||
v-if="social.platform && env.socials.urls[social.platform]"
|
||||
:icon="iconMap[social.platform] || social.platform"
|
||||
:title="social.platform"
|
||||
:class="`icon-social icon-${social.platform}`"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-else-if="social.platform"
|
||||
icon="bubbles10"
|
||||
:title="social.platform"
|
||||
:class="`icon-social icon-${social.platform} icon-generic`"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-else-if="social.url"
|
||||
icon="sphere"
|
||||
:title="social.platform"
|
||||
:class="`icon-social icon-${social.platform} icon-generic`"
|
||||
/>
|
||||
|
||||
{{ social.handle || social.url }}
|
||||
</a>
|
||||
|
||||
<Icon
|
||||
v-if="!socials.some((listItem) => listItem.social === social.social || listItem.url === social.url)"
|
||||
icon="checkmark"
|
||||
class="add noselect"
|
||||
@click="socials = socials.concat(social)"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-else
|
||||
icon="cross2"
|
||||
class="remove noselect"
|
||||
@click="socials = socials.filter((listItem, listIndex) => listIndex !== index)"
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li class="list-new">
|
||||
<VDropdown>
|
||||
<Icon
|
||||
icon="plus2"
|
||||
class="add noselect"
|
||||
/>
|
||||
|
||||
<template #popper>
|
||||
<form
|
||||
class="new"
|
||||
@submit.prevent="addSocial"
|
||||
>
|
||||
<div class="new-section">
|
||||
<input
|
||||
v-model="platform"
|
||||
list="platforms"
|
||||
class="input"
|
||||
placeholder="Platform"
|
||||
pattern="[a-z]+"
|
||||
>
|
||||
|
||||
<datalist id="platforms">
|
||||
<option value="onlyfans">OnlyFans</option>
|
||||
<option value="twitter">Twitter/X</option>
|
||||
<option value="instagram">Instagram</option>
|
||||
<option value="pornhub">PornHub</option>
|
||||
<option value="linktree">Linktree</option>
|
||||
<option value="fansly">Fansly</option>
|
||||
<option value="loyalfans">LoyalFans</option>
|
||||
<option value="manyvids">ManyVids</option>
|
||||
<option value="cashapp">Cash App</option>
|
||||
</datalist>
|
||||
|
||||
<input
|
||||
v-model="handle"
|
||||
class="input"
|
||||
placeholder="Handle"
|
||||
pattern="[\w-]+"
|
||||
:disabled="!!url"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="new-section">
|
||||
OR<input
|
||||
v-model="url"
|
||||
class="input"
|
||||
placeholder="Website URL"
|
||||
:disabled="!!handle"
|
||||
>
|
||||
|
||||
<button
|
||||
class="button"
|
||||
>Add</button>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
</VDropdown>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
watch,
|
||||
inject,
|
||||
} from 'vue';
|
||||
|
||||
import formatTemplate from 'template-format';
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const { env } = pageContext;
|
||||
|
||||
const props = defineProps({
|
||||
edits: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
editing: {
|
||||
type: Set,
|
||||
default: null,
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['socials']);
|
||||
const socials = ref(props.edits.socials);
|
||||
|
||||
const platform = ref('');
|
||||
const handle = ref('');
|
||||
const url = ref('');
|
||||
|
||||
watch(socials, () => emit('socials', socials));
|
||||
|
||||
const iconMap = {
|
||||
twitter: 'twitter-x',
|
||||
};
|
||||
|
||||
function addSocial() {
|
||||
if (!handle.value && !url.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (handle.value && !platform.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
socials.value = socials.value.concat({
|
||||
platform: platform.value || null,
|
||||
handle: handle.value || null,
|
||||
url: url.value || null,
|
||||
});
|
||||
|
||||
emit('socials', socials.value);
|
||||
|
||||
platform.value = 'onlyfans';
|
||||
handle.value = '';
|
||||
url.value = '';
|
||||
}
|
||||
|
||||
function getUrl(social) {
|
||||
if (social.url) {
|
||||
return url;
|
||||
}
|
||||
|
||||
if (env.socials.urls[social.platform]) {
|
||||
return formatTemplate(env.socials.urls[social.platform], { handle: social.handle });
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.list {
|
||||
display: flex;
|
||||
gap: .5rem;
|
||||
|
||||
&.disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.list-item {
|
||||
.icon-social {
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
.icon-generic {
|
||||
fill: var(--glass-strong-20);
|
||||
}
|
||||
|
||||
&.deleted {
|
||||
color: var(--glass);
|
||||
text-decoration: line-through;
|
||||
|
||||
.icon.icon-social {
|
||||
fill: var(--glass-weak-10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-item,
|
||||
.list-new {
|
||||
display: inline-flex;
|
||||
align-items: stretch;
|
||||
border-radius: .25rem;
|
||||
box-shadow: 0 0 3px var(--shadow-weak-30);
|
||||
background: var(--background);
|
||||
|
||||
.link {
|
||||
padding: .25rem 0 .25rem .5rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.add,
|
||||
.remove {
|
||||
padding: 0 .3rem;
|
||||
margin-left: .5rem;
|
||||
border-radius: .25rem;
|
||||
|
||||
&:hover {
|
||||
fill: var(--text-light);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.add {
|
||||
fill: var(--success);
|
||||
|
||||
&:hover {
|
||||
background: var(--success);
|
||||
}
|
||||
}
|
||||
|
||||
.remove {
|
||||
fill: var(--error);
|
||||
|
||||
&:hover {
|
||||
background: var(--error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-new .add {
|
||||
padding: .25rem .5rem;
|
||||
background: var(--background);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.new {
|
||||
padding: .25rem;
|
||||
}
|
||||
|
||||
.new-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
padding: .25rem;
|
||||
|
||||
.input {
|
||||
flex-grow: 1;
|
||||
|
||||
&:disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -66,6 +66,23 @@ module.exports = {
|
|||
bans: {
|
||||
defaultExpiry: 60 * 24 * 3, // in minutes, 3 days
|
||||
},
|
||||
socials: {
|
||||
urls: {
|
||||
cashapp: 'https://cash.app/${handle}', // eslint-disable-line no-template-curly-in-string
|
||||
fansly: 'https://fansly.com/{handle}',
|
||||
linktree: 'https://linktr.ee/{handle}',
|
||||
loyalfans: 'https://www.loyalfans.com/{handle}',
|
||||
manyvids: 'https://www.manyvids.com/Profile/{handle}/slug/Store/Videos',
|
||||
onlyfans: 'https://onlyfans.com/{handle}',
|
||||
pornhub: 'https://www.pornhub.com/model/{handle}',
|
||||
twitter: 'https://x.com/{handle}',
|
||||
},
|
||||
prefix: {
|
||||
default: '@',
|
||||
cashapp: '$',
|
||||
reddit: 'u/',
|
||||
},
|
||||
},
|
||||
apiAccess: {
|
||||
graphqlEnabled: true,
|
||||
keySize: 24, // bytes
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"redis": "^4.6.12",
|
||||
"sharp": "^0.32.6",
|
||||
"sirv": "^2.0.3",
|
||||
"template-format": "^1.2.5",
|
||||
"unprint": "^0.14.1",
|
||||
"video.js": "^8.10.0",
|
||||
"vike": "^0.4.150",
|
||||
|
@ -10316,6 +10317,11 @@
|
|||
"node": ">=8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/template-format": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/template-format/-/template-format-1.2.5.tgz",
|
||||
"integrity": "sha512-ZZqSfqYBMfPjouADYSRN9iaYlLr2PPVFYgULcV8cGMrJbifNXKvP7qx5PBFQjXg5mh1Gwkk+LTgdsZ8bmSvBdw=="
|
||||
},
|
||||
"node_modules/text-hex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
|
||||
|
@ -18962,6 +18968,11 @@
|
|||
"resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz",
|
||||
"integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ=="
|
||||
},
|
||||
"template-format": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/template-format/-/template-format-1.2.5.tgz",
|
||||
"integrity": "sha512-ZZqSfqYBMfPjouADYSRN9iaYlLr2PPVFYgULcV8cGMrJbifNXKvP7qx5PBFQjXg5mh1Gwkk+LTgdsZ8bmSvBdw=="
|
||||
},
|
||||
"text-hex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
"redis": "^4.6.12",
|
||||
"sharp": "^0.32.6",
|
||||
"sirv": "^2.0.3",
|
||||
"template-format": "^1.2.5",
|
||||
"unprint": "^0.14.1",
|
||||
"video.js": "^8.10.0",
|
||||
"vike": "^0.4.150",
|
||||
|
|
|
@ -217,48 +217,13 @@
|
|||
</span>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
v-if="item.type === 'list'"
|
||||
class="list nolist"
|
||||
:class="{ disabled: !editing.has(item.key) }"
|
||||
>
|
||||
<li
|
||||
v-for="(value, index) in item.value"
|
||||
:key="`${item.type}-${value}`"
|
||||
class="list-item"
|
||||
:class="{ deleted: !edits[item.key].some((listItem) => listItem.value === value.value || listItem.url === value.url) }"
|
||||
>
|
||||
<Icon
|
||||
v-if="value.icon"
|
||||
:icon="value.icon"
|
||||
:class="`icon-social icon-${value.icon}`"
|
||||
<EditSocials
|
||||
v-if="item.type === 'socials'"
|
||||
:edits="edits"
|
||||
:editing="editing"
|
||||
@socials="(socials) => edits.socials = socials"
|
||||
/>
|
||||
|
||||
<a
|
||||
v-if="value.url"
|
||||
:href="value.url"
|
||||
target="_blank"
|
||||
class="link"
|
||||
>{{ value.url }}</a>
|
||||
|
||||
<template v-else>{{ value.value || value }}</template>
|
||||
|
||||
<Icon
|
||||
v-if="!edits[item.key].some((listItem) => listItem.value === value.value || listItem.url === value.url)"
|
||||
icon="checkmark"
|
||||
class="add noselect"
|
||||
@click="edits[item.key] = edits[item.key].concat(value)"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-else
|
||||
icon="cross2"
|
||||
class="remove noselect"
|
||||
@click="edits[item.key] = edits[item.key].filter((listItem, listIndex) => listIndex !== index)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<EditPlace
|
||||
v-if="item.type === 'place'"
|
||||
:item="item"
|
||||
|
@ -373,6 +338,7 @@
|
|||
import { ref, computed, inject } from 'vue';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
import EditSocials from '#/components/edit/socials.vue';
|
||||
import EditPlace from '#/components/edit/place.vue';
|
||||
import EditFigure from '#/components/edit/figure.vue';
|
||||
import EditAugmentation from '#/components/edit/augmentation.vue';
|
||||
|
@ -423,16 +389,11 @@ const fields = computed(() => [
|
|||
: null,
|
||||
inline: true,
|
||||
},
|
||||
/*
|
||||
{
|
||||
key: 'socials',
|
||||
type: 'list',
|
||||
value: actor.value.socials.map((social) => ({
|
||||
url: social.url,
|
||||
icon: social.platform,
|
||||
})),
|
||||
type: 'socials',
|
||||
value: actor.value.socials,
|
||||
},
|
||||
*/
|
||||
{
|
||||
key: 'origin',
|
||||
type: 'place',
|
||||
|
@ -647,7 +608,7 @@ async function submit() {
|
|||
actorId: actor.value.id,
|
||||
edits: {
|
||||
...Object.fromEntries(Array.from(editing.value).flatMap((key) => {
|
||||
if (edits.value[key] && typeof edits.value[key] === 'object') {
|
||||
if (edits.value[key] && typeof edits.value[key] === 'object' && !Array.isArray(edits.value[key])) {
|
||||
return Object.entries(edits.value[key]).map(([valueKey, value]) => [keyMap[key]?.[valueKey] || valueKey, value]);
|
||||
}
|
||||
|
||||
|
@ -844,61 +805,6 @@ async function submit() {
|
|||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
&.disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
}
|
||||
|
||||
.list-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: .25rem;
|
||||
background: var(--background);
|
||||
box-shadow: 0 0 3px var(--shadow-weak-30);
|
||||
|
||||
.icon-social {
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
|
||||
&.deleted {
|
||||
color: var(--glass);
|
||||
text-decoration: line-through;
|
||||
|
||||
.icon.icon-social {
|
||||
fill: var(--glass-weak-10);
|
||||
}
|
||||
}
|
||||
|
||||
.add,
|
||||
.remove {
|
||||
padding: .25rem .3rem;
|
||||
margin-left: .5rem;
|
||||
border-radius: .25rem;
|
||||
|
||||
&:hover {
|
||||
fill: var(--text-light);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.add {
|
||||
fill: var(--success);
|
||||
|
||||
&:hover {
|
||||
background: var(--success);
|
||||
}
|
||||
}
|
||||
|
||||
.remove {
|
||||
fill: var(--error);
|
||||
|
||||
&:hover {
|
||||
background: var(--error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.avatars {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
|
166
src/actors.js
|
@ -55,6 +55,8 @@ const keyMap = {
|
|||
isCircumcised: 'circumcised',
|
||||
};
|
||||
|
||||
const socialsOrder = ['onlyfans', 'twitter'];
|
||||
|
||||
export function curateActor(actor, context = {}) {
|
||||
return {
|
||||
id: actor.id,
|
||||
|
@ -115,9 +117,11 @@ export function curateActor(actor, context = {}) {
|
|||
agency: actor.agency,
|
||||
avatar: curateMedia(actor.avatar),
|
||||
socials: context.socials?.map((social) => ({
|
||||
id: social.id,
|
||||
url: social.url,
|
||||
platform: social.platform,
|
||||
})),
|
||||
handle: social.handle,
|
||||
})).toSorted((socialA, socialB) => socialsOrder.indexOf(socialB.platform) - socialsOrder.indexOf(socialA.platform)),
|
||||
profiles: context.profiles?.map((profile) => ({
|
||||
id: profile.id,
|
||||
description: profile.description,
|
||||
|
@ -216,7 +220,7 @@ export async function fetchActorsById(actorIds, options = {}, reqUser) {
|
|||
.leftJoin('media', 'media.id', 'actors_avatars.media_id')
|
||||
.groupBy('media.id', 'actors_avatars.actor_id')
|
||||
.orderBy(knex.raw('max(actors_avatars.created_at)'), 'desc'),
|
||||
knex('actors_social')
|
||||
knex('actors_socials')
|
||||
.whereIn('actor_id', actorIds),
|
||||
reqUser
|
||||
? knex('stashes_actors')
|
||||
|
@ -527,14 +531,14 @@ export async function fetchActorRevisions(revisionId, filters = {}, reqUser) {
|
|||
}
|
||||
|
||||
async function applyActorValueDelta(profileId, delta, trx) {
|
||||
return knex('actors_profiles')
|
||||
await knex('actors_profiles')
|
||||
.where('id', profileId)
|
||||
.update(keyMap[delta.key] || delta.key, delta.value)
|
||||
.transacting(trx);
|
||||
}
|
||||
|
||||
async function applyActorDirectDelta(actorId, delta, trx) {
|
||||
return knex('actors')
|
||||
await knex('actors')
|
||||
.where('id', actorId)
|
||||
.update(keyMap[delta.key] || delta.key, delta.value)
|
||||
.modify((builder) => {
|
||||
|
@ -545,6 +549,22 @@ async function applyActorDirectDelta(actorId, delta, trx) {
|
|||
.transacting(trx);
|
||||
}
|
||||
|
||||
async function applyActorSocialsDelta(actorId, delta, trx) {
|
||||
await knex('actors_socials')
|
||||
.where('actor_id', actorId)
|
||||
.delete()
|
||||
.transacting(trx);
|
||||
|
||||
await knex('actors_socials')
|
||||
.insert(delta.value.map((social) => ({
|
||||
actor_id: actorId,
|
||||
platform: social.platform,
|
||||
handle: social.handle,
|
||||
url: social.url,
|
||||
})))
|
||||
.transacting(trx);
|
||||
}
|
||||
|
||||
async function fetchMainProfile(actorId, wasCreated = false) {
|
||||
const profileEntry = await knex('actors_profiles')
|
||||
.where('actor_id', actorId)
|
||||
|
@ -623,6 +643,10 @@ async function applyActorRevision(revisionIds, reqUser) {
|
|||
return applyActorValueDelta(mainProfile.id, delta, trx);
|
||||
}
|
||||
|
||||
if (delta.key === 'socials') {
|
||||
return applyActorSocialsDelta(revision.actor_id, delta, trx);
|
||||
}
|
||||
|
||||
if (delta.key === 'name' && reqUser.role === 'admin') {
|
||||
return applyActorDirectDelta(revision.actor_id, delta, trx);
|
||||
}
|
||||
|
@ -767,35 +791,59 @@ function convertWeight(weight, units) {
|
|||
return Number(weight) || null;
|
||||
}
|
||||
|
||||
export async function createActorRevision(actorId, {
|
||||
edits,
|
||||
comment,
|
||||
apply,
|
||||
...options
|
||||
}, reqUser) {
|
||||
const [
|
||||
[actor],
|
||||
openRevisions,
|
||||
] = await Promise.all([
|
||||
fetchActorsById([actorId], {
|
||||
reqUser,
|
||||
includeAssets: true,
|
||||
includePartOf: true,
|
||||
}),
|
||||
knex('actors_revisions')
|
||||
.where('user_id', reqUser.id)
|
||||
.whereNull('approved'),
|
||||
]);
|
||||
const platformsByHostname = Object.fromEntries(Object.entries(config.socials.urls).map(([platform, url]) => {
|
||||
const { hostname, pathname } = new URL(url);
|
||||
|
||||
if (!actor) {
|
||||
throw new HttpError(`No actor with ID ${actorId} found to update`, 404);
|
||||
return [hostname, {
|
||||
platform,
|
||||
pathname: decodeURIComponent(pathname),
|
||||
url,
|
||||
}];
|
||||
}));
|
||||
|
||||
function curateSocials(socials) {
|
||||
return socials.map((social) => {
|
||||
if (!social.handle && !social.url) {
|
||||
throw new Error('No social handle or website URL specified');
|
||||
}
|
||||
|
||||
if (openRevisions.length >= config.revisions.unapprovedLimit && reqUser.role !== 'admin') {
|
||||
throw new HttpError(`You have ${config.revisions.unapprovedLimit} unapproved revisions, please wait for approval before submitting another revision.`, 429);
|
||||
if (social.handle && !social.platform) {
|
||||
throw new Error('No platform specified for social handle');
|
||||
}
|
||||
|
||||
const baseActor = Object.fromEntries(Object.entries(actor).map(([key, values]) => {
|
||||
if (social.handle && social.platform && /[\w-]+/.test(social.handle) && /[a-z]+/i.test(social.platform)) {
|
||||
return {
|
||||
platform: social.platform.toLowerCase(),
|
||||
handle: social.handle,
|
||||
};
|
||||
}
|
||||
|
||||
if (social.url) {
|
||||
const { hostname, pathname } = new URL(social.url);
|
||||
const platform = platformsByHostname[hostname];
|
||||
|
||||
if (platform) {
|
||||
const handle = pathname.match(new RegExp(platform.pathname.replace('{handle}', '([\\w-]+)')))?.[1];
|
||||
|
||||
if (handle) {
|
||||
return {
|
||||
platform: platform.platform,
|
||||
handle,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
url: social.url,
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error('Invalid social');
|
||||
}).filter(Boolean);
|
||||
}
|
||||
|
||||
function getBaseActor(actor) {
|
||||
return Object.fromEntries(Object.entries(actor).map(([key, values]) => {
|
||||
if ([
|
||||
'scenes',
|
||||
'likes',
|
||||
|
@ -805,11 +853,11 @@ export async function createActorRevision(actorId, {
|
|||
return null;
|
||||
}
|
||||
|
||||
/* avatar should return id
|
||||
if (values?.hash) {
|
||||
return [key, values.hash];
|
||||
if ([
|
||||
'socials',
|
||||
].includes(key)) {
|
||||
return [key, values];
|
||||
}
|
||||
*/
|
||||
|
||||
if (values?.id) {
|
||||
return [key, values.id];
|
||||
|
@ -825,8 +873,10 @@ export async function createActorRevision(actorId, {
|
|||
|
||||
return [key, values];
|
||||
}).filter(Boolean));
|
||||
}
|
||||
|
||||
const deltas = await Promise.all(Object.entries(edits).map(async ([key, value]) => {
|
||||
function getDeltas(edits, baseActor, options) {
|
||||
return Promise.all(Object.entries(edits).map(async ([key, value]) => {
|
||||
if (baseActor[key] === value || typeof value === 'undefined') {
|
||||
return null;
|
||||
}
|
||||
|
@ -890,6 +940,24 @@ export async function createActorRevision(actorId, {
|
|||
];
|
||||
}
|
||||
|
||||
if (key === 'socials') {
|
||||
const convertedSocials = curateSocials(value);
|
||||
|
||||
const convertedUrls = value
|
||||
.filter((social) => social.url && !convertedSocials.some((convertedSocial) => convertedSocial.url === social.url))
|
||||
.map((social) => social.url);
|
||||
|
||||
const conversionComment = convertedUrls.length > 0
|
||||
? `curated URLs ${convertedUrls.join(', ')} as social handles`
|
||||
: null;
|
||||
|
||||
return {
|
||||
key,
|
||||
value: convertedSocials,
|
||||
comment: conversionComment,
|
||||
};
|
||||
}
|
||||
|
||||
if (['cup', 'bust', 'waist', 'hip'].includes(key)) {
|
||||
const convertedValue = convertFigure(key, value, options.figureUnits);
|
||||
|
||||
|
@ -967,6 +1035,38 @@ export async function createActorRevision(actorId, {
|
|||
|
||||
return { key, value };
|
||||
})).then((rawDeltas) => rawDeltas.flat().filter(Boolean));
|
||||
}
|
||||
|
||||
export async function createActorRevision(actorId, {
|
||||
edits,
|
||||
comment,
|
||||
apply,
|
||||
...options
|
||||
}, reqUser) {
|
||||
const [
|
||||
[actor],
|
||||
openRevisions,
|
||||
] = await Promise.all([
|
||||
fetchActorsById([actorId], {
|
||||
reqUser,
|
||||
includeAssets: true,
|
||||
includePartOf: true,
|
||||
}),
|
||||
knex('actors_revisions')
|
||||
.where('user_id', reqUser.id)
|
||||
.whereNull('approved'),
|
||||
]);
|
||||
|
||||
if (!actor) {
|
||||
throw new HttpError(`No actor with ID ${actorId} found to update`, 404);
|
||||
}
|
||||
|
||||
if (openRevisions.length >= config.revisions.unapprovedLimit && reqUser.role !== 'admin') {
|
||||
throw new HttpError(`You have ${config.revisions.unapprovedLimit} unapproved revisions, please wait for approval before submitting another revision.`, 429);
|
||||
}
|
||||
|
||||
const baseActor = getBaseActor(actor);
|
||||
const deltas = await getDeltas(edits, baseActor, options);
|
||||
|
||||
const deltaComments = deltas.map((delta) => delta.comment);
|
||||
const curatedComment = [comment, ...deltaComments].filter(Boolean).join(' | ');
|
||||
|
|
|
@ -42,6 +42,7 @@ export default async function mainHandler(req, res, next) {
|
|||
media: config.media,
|
||||
psa: config.psa,
|
||||
links: config.links,
|
||||
socials: config.socials,
|
||||
},
|
||||
meta: {
|
||||
now: new Date().toISOString(),
|
||||
|
|