<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Sophia Ly on Medium]]></title>
        <description><![CDATA[Stories by Sophia Ly on Medium]]></description>
        <link>https://medium.com/@SophiaCactuar?source=rss-d0e7f6c2a314------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*HSjuAOYmFswk0yRcluceYA.jpeg</url>
            <title>Stories by Sophia Ly on Medium</title>
            <link>https://medium.com/@SophiaCactuar?source=rss-d0e7f6c2a314------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sun, 24 May 2026 02:24:39 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@SophiaCactuar/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Transitioning at 40]]></title>
            <link>https://medium.com/@SophiaCactuar/transitioning-at-40-01bc8c926fb3?source=rss-d0e7f6c2a314------2</link>
            <guid isPermaLink="false">https://medium.com/p/01bc8c926fb3</guid>
            <category><![CDATA[transitions]]></category>
            <category><![CDATA[gender]]></category>
            <category><![CDATA[authenticity]]></category>
            <category><![CDATA[trans-women]]></category>
            <category><![CDATA[transgender]]></category>
            <dc:creator><![CDATA[Sophia Ly]]></dc:creator>
            <pubDate>Sat, 28 Jun 2025 22:01:11 GMT</pubDate>
            <atom:updated>2025-06-28T22:01:11.771Z</atom:updated>
            <content:encoded><![CDATA[<h4>My experience transitioning from man to woman</h4><p>As I enter my fourth decade of my life, the vast majority of which was spent living as a man, I’ve finally come to terms with the fact that I feel my most authentic self as a woman.</p><p>But what does that mean? We can start with the basic definition.</p><blockquote>A <strong>transgender</strong> (often shortened to <strong>trans</strong>) person has a <a href="https://en.wikipedia.org/wiki/Gender_identity">gender identity</a> different from that typically associated with the <a href="https://en.wikipedia.org/wiki/Sex">sex</a> they were <a href="https://en.wikipedia.org/wiki/Sex_assignment">assigned at birth</a>. The opposite of <em>transgender</em> is <a href="https://en.wikipedia.org/wiki/Cisgender"><em>cisgender</em></a>, which describes persons whose gender identity matches their assigned sex.</blockquote><p>That’s the basic definition for a foundational understanding. Each individual’s experience is unique, and I can only speak of my own experience, and where appropriate compare and contrast with experiences I’ve heard from others.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*5T8joNfVCiEtORU-" /><figcaption>At my 40th birthday party where I first announced my transition to my friends.</figcaption></figure><h4>How did you know you were trans?</h4><p>There wasn’t really a specific moment. It was more of a gradual thing for me. Everyone’s experience is a bit different, but I’d say I didn’t have the “typical” experience where I felt gender dysphoria and was unhappy with or felt bad about being a man. I think that contributes to why it took me so long to come to my realization.</p><p>One of the things that really helped for me was people pointing out that the story of trans people isn’t just about all the bad things, and dysphoria isn’t a necessary criteria. You don’t have to feel miserable about yourself to want to change. Gender euphoria is equally if not more important. It’s not about being unhappy with who you are, but it’s about being happy and joyful about who you can become.</p><p>An important sidenote: I use the term “become” and “who you ae/were” because that is what reflects my own experience. For many others the experience is less about “becoming” something else, and more about not hiding who they truly are. There is no one right way to be trans.</p><h4>Were there any early signs?</h4><p>Growing up as a kid, I always got along better with girls than I did boys. Nothing really overt, but just overall I related more with girls than boys. At least until the age when society differentiates more between boys and girls.</p><p>One year in either middle school or high school there was a gender-bender day and I wore one of my mom’s dresses. I remember that feeling good, although I don’t think I really mentioned that part to anyone.</p><p>One interesting sign that I didn’t quite understand at the time, is that I have a strong memory of thinking that I wanted to be a lesbian. At this age I hadn’t heard of the term transgender before, so it was kind of a nonsensical thought to me. I understood that some people were gay, but I was a boy that liked girls so what could I be but straight? What would it even mean for me to be a lesbian, that doesn’t make sense. In hindsight, I think this was probably my biggest sign, but of course it didn’t make sense at the time so I kind of ignored it, but it was apparently a core memory that stuck with me, even though I didn’t tell anyone about it.</p><h4>How does it feel transition later in life?</h4><p>While I’m not the older person to transition by a long shot, these days, more people are figuring things out sooner and more openly exploring gender and transitioning in their teens or twenties, and I’m super happy for them all. I see a lot of comments online from people who feel like they’re too old to transition, or that they feel the time has passed, to which I say that’s nonsense. Not in a dismissive way, but in an empowering way. You’re never too old to explore and become who you are meant to be.</p><p>For my own experience, I know a lot of trans people will say they wish they had done it sooner, and it’s very likely for them that may have been the right thing. But for me, I take the Gandalf approach. “A wizard is never late, [s]he arrives precisely when [s]he means to.” Since I didn’t experience dysphoria, I could take my time to figure things out. I think one of the benefits of transitioning later is that you’ve had more time to mature. When I do girly things, it’s less because society says I should and more because I want to (although there isn’t any 100% escape from societal pressures). Overall, I think I’m more comfortable with myself and who I am.</p><h4>What was the process leading up to your transition look like?</h4><p>I’m lucky that I have both friends and a workplace that is very supportive, so I didn’t have to transition in secret.</p><p>At home, when the beginning of the COVID-19 pandemic, I started wearing dresses and then corsets. I liked the way they made me feel. I felt pretty. I’ve never disliked my body, but I began to actively like my body and how I looked.</p><p>As for work when we moved to hybrid working, I started testing the waters. First at a Pride event, and then at the company holiday party, I wore women’s clothes. This extended to any kind of fancy (internal, non-client-facing) event at work, and eventually just on normal days.</p><p>For years, during this process I was trying to figure out how to describe myself. Many would suggest “non-binary” but that didn’t feel right for me. Being slightly autistic, the literalist in me would say, that’s the opposite of what I am, I’m actually binary, both, but not at the same time. I eventually settled on genderfluid which felt more right to me. I later learned of the term bi-gender but by that point I was happy with genderfluid. Side note: All of these technically fall under the trans umbrella term, but it wasn’t a term I adopted for myself until “fully” transitioning. I put “fully” in quotes because it reflects my own experience, but others may describe it differently.</p><p>Throughout this process, a few times a year I would ask myself, am I actually trans and just in denial? The answer every time was “I don’t think so, but maybe.” The trans community describes this period of questioning as being an “egg” waiting to hatch. I think it’s an apt metaphor, and I’m grateful that no one tried to make me “hatch” prematurely, I’m ready when I’m ready.</p><h4>How did the actual transition process happen?</h4><p>As I was going about my life, I had a thought about what my name would be if I actually was trans. I think subconsciously part of what was holding me back was that I didn’t have a name. The name Ophelia came to mind, and it kind of stuck with me. I actually tried it out as my name in a mobile game, that unfortunately didn’t keep my interest longer than a couple weeks, so I didn’t get to spend more time with the name. (I changed it to Sophia about a week after trying on Ophelia because it was too many syllables and no easy nicknames)</p><p>Since my 40th birthday was coming up, and I had a party planned, I had the thought that maybe I should make the announcement at the party. The party had been planned for months, but this idea only came about a couple weeks before the party. And so, I did exactly that. Everyone was super supportive, and it was a great time.</p><p>At this point I hadn’t actually given a whole lot of thought to medical transitioning. I know a lot of people start medically transitioning before socially transitioning, but I was in a safe enough environment that I could do it the other way around.</p><p>I was debating when to transition at work, and a couple weeks later I had mentioned it to my LGBTQ co-workers, and since it was June, we were planning to have a Pride panel, and thought that would be a good time to announce it. I talked with HR and scheduled to have things change over on that day. There were some logistical and other issues that caused the panel to have to be rescheduled, but I kept the same day for announcing it. I drafted a short email and sent it out. The reception was tremendously positive, and I had co-workers from around the globe congratulating me, which was a great feeling. I knew things would go well because my company has been very supportive, but I was still nervous about clicking send.</p><h4>What did medical transition look like to you?</h4><p>A couple weeks after socially transitioning I started to give serious thought and research into HRT (Hormone Replacement Therapy). It didn’t take long after that to know that that was what I wanted to do, and about a week later I scheduled an appointment at Planned Parenthood.</p><p>In my research, I didn’t really look into anything Minnesota specific, as I knew MN was very trans friendly and I didn’t expect any issues. Still, I expected my first visit to be consultative and expected some amount of paperwork would be needed. To my surprise, I learned that MN only requires informed consent, that is that I know what the effects and side effects are, and that I consent to treatment. And so, I left the appointment with a prescription. I was further surprised at how cheap estrogen is, even without insurance. I opted for only estrogen and skipped Spiro, a testosterone blocker, due to potential side effects and the latest consensus among the trans community that it isn’t needed for most people.</p><p>I’ve only been on E for a little less than a month, but I’ve already started to notice a few things.</p><p>First, in the first week or so I was retaining a lot more water and even though I was drinking a lot of water, I wasn’t peeing nearly as much as I would have normally. This normalized after a week or so.</p><p>Second, my skin is noticeably softer. I’ve always had fairly soft skin, but it’s even softer now and I find myself touching my arms and face because of how soft it feels.</p><p>Third, a couple days ago, I noticed my breasts got a growth spurt where it grew a noticeable amount almost overnight.</p><p>Fourth is that undesired erections pretty much went away almost immediately. They weren’t that common to begin with, but now it never happens, which is great.</p><p>I haven’t noticed any mood changes yet, and as someone who has difficulty crying, I am actually looking forward to being able to cry more easily.</p><p>Most of the effects usually say they start in 3–6 months and can take 3–5 years for full effect, but I am starting to see some changes already less than 1 month in.</p><p>While it can slow down growth of facial and body hair (I didn’t really have any body hair to begin with) it doesn’t go away completely, so I’m getting laser hair removal for my facial hair as well.</p><p>One notable thing that it DOESN’T help with is voice. I had known that years ago, but it was deeply disappointing to learn that I had to train my voice myself and it didn’t magically change on its own. I watched a few videos for tips and tricks, but I mostly practiced singing on my own, and now that I’m fully socially transitioned, I’m practicing every day, but not currently taking any lessons or anything.</p><h4>How do you feel being a trans woman?</h4><p>Honestly, pretty great!</p><p>In many ways I feel incredibly lucky because I got to grow up with the confidence of a man and got to partake in male privilege growing up. I also didn’t have to go through puberty as a women (although I do expect in the coming months/years I will go through what people call “second puberty”). I also don’t have to worry about periods. In many ways it feels like I have an unfair advantage.</p><p>I will say that my experience is probably a bit atypical, especially among my peer group. I didn’t have to deal with gender dysphoria and the related mental health issues. I was able to start and advance through a financially stable career and while I’m not rich, I’m in a comfortable position financially. I have friends, family, and co-workers who are supportive. I haven’t heard a single negative remark about my transition [yet, I’m sure it’ll happen at some point]. I’m also lucky that I’m fairly close to “passing” even before starting HRT and without make up, so I don’t experience feeling bad about my body. All of these are things that a lot of my trans friends struggle with, so I’m quite lucky with how life turned out for me.</p><p>My mental health had already been slowly improving over the past 2 decades, so I can’t fully attribute it to my transition. That being said, it did improve a noticeable amount when I started identifying as genderfluid, and now that I’ve “fully” transitioned, I’d say on average I’m the happiest I’ve ever been, and my depression is pretty much non-existent at this point.</p><p>I honestly feel like I have the best of both worlds, and I sometimes feel slightly guilty about it because I know how many people out there are suffering, both people I know and don’t know personally.</p><h4>Do you regret anything about transitioning or miss anything about being a man?</h4><p>It’s still new to me, so things may change, but honestly at the moment, nothing.</p><p>Okay, I do miss having functional pockets, but I have gotten used to carrying a purse so it’s not too bad, just a tad inconvenient.</p><p>I guess another thing I miss is being able to grab a random shirt and pair of pants and call it good. Having to think about pieces matching is a new experience and kind of annoying at times, but at least right now, the trade off of looking pretty is worth it. I’m sure that might change over time.</p><p>On the topic of clothing, kind of the opposite of regret, but did you know that women’s jeans are stretchy??? They feel so good and comfy, why aren’t men’s jeans also stretchy?</p><p>I haven’t started wearing makeup yet, mostly because it’s a bit intimidating, but I’ll figure it out. I’m sure that might get annoying, but I can also choose not to.</p><h4>Do you feel safe in the current political climate?</h4><p>That’s a difficult question to answer. There has been a rise in transphobia and anti-trans violence that is undeniable. There are a lot of very valid fears that people have, and a lot of anger over the stripping of legal protections.</p><p>For me personally, I’m lucky to have a lot of factors that work in my favor, from financial stability to being in a supportive environment, and living in a trans-friendly state. Even though I live in a suburb in a county that skews red, I haven’t experienced any animosity going about my daily life.</p><p>It was a small part of the reason, but part of the reason for “why now?” in transitioning is a bit of an “f-you” to the forces of hate. I want to be visible, not in an “in your face” way, but in an “I’m going to live my life and not hide” way. Because at the end of the day, we’re just people who want to live our lives. And the most people see that we exist, and we’re just people, the easier it is to accept, and the harder it is to paint someone as a boogeyman.</p><h3>Transition photos</h3><p>Some photos from various stages of my transition.</p><h4>Pre-transition (2019)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/720/0*HE9EA2tgbu6SrM7J" /><figcaption>I don’t remember where this was from</figcaption></figure><h4>Exploring femininity (2020)</h4><figure><img alt="Me sitting in a window sill with a black dress" src="https://cdn-images-1.medium.com/max/720/0*Adi7QuensBzhpoNb" /><figcaption>One of the first dresses I got, but still one of my favorites</figcaption></figure><h4>Continued (2021)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*QoFT3mk3J3PBHtit" /><figcaption>Braiding my hair</figcaption></figure><h4>Continued (2022)</h4><figure><img alt="Me in a black dress with a purple steampunk corset in a bathroom mirror." src="https://cdn-images-1.medium.com/max/1024/0*-SbKXinT-deLvWVm" /><figcaption>When I started wearing corsets</figcaption></figure><h4>Continued (2023)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sg81RIrmF6GuFttp" /><figcaption>Skirt and bralette</figcaption></figure><h4>Shaving my facial hair (2024)</h4><p>In 2024 I shaved my face. It was a bit difficult because at the time I was still insistent on being genderfluid and it was one of ties to being a man and my masculine side.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*FjCcD-T-zmAbv3nw" /><figcaption>Hair dyed purple and face shaved</figcaption></figure><h4>Right before “officially” transitioning (2025)</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*bnT4s3q6Gms2KPpI" /><figcaption>At my friend’s 50th birthday party</figcaption></figure><p>After transitioning (2025)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*MjUxpABKNJFER1FW" /><figcaption>At my birthday party where I announced my transition</figcaption></figure><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=01bc8c926fb3" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Flames of Death, Flames of Life]]></title>
            <link>https://medium.com/@SophiaCactuar/flames-of-death-flames-of-life-0dc96a9a36eb?source=rss-d0e7f6c2a314------2</link>
            <guid isPermaLink="false">https://medium.com/p/0dc96a9a36eb</guid>
            <category><![CDATA[hope]]></category>
            <category><![CDATA[resistance]]></category>
            <category><![CDATA[current-affairs]]></category>
            <category><![CDATA[united-states]]></category>
            <category><![CDATA[fascism]]></category>
            <dc:creator><![CDATA[Sophia Ly]]></dc:creator>
            <pubDate>Wed, 16 Apr 2025 03:02:23 GMT</pubDate>
            <atom:updated>2025-04-16T03:02:23.696Z</atom:updated>
            <content:encoded><![CDATA[<p>The world, familiar, yet wholly changed. We live within our bubble, both oblivious to, and keenly aware of the roiling sea in which we float. We see others in their own bubbles, floating alongside us. We watch some pop, the inhabitants swept away by the merciless waves, and yet we maintain the illusion of our own safety.</p><p>Yet this roiling, boiling, turbulent sea is not a phenomenon of nature but of man’s making. An evil which, at one time lurked and crept in the shadows, now manifests itself openly in the light; no longer needing to shroud itself in subtlety and subterfuge. It stokes the flames of of chaos, white and hot, boiling the great cauldron that is our nation, our world, fueled by depthless malice.</p><p>How does one process our current state of affairs? I am at once intellectually outraged, yet emotionally numb. Another human unlawfully detained, exiled, deprived of rights, and out of reach of the law and due process that ought to be afforded to every person. Snatched off the streets, kidnapped, whisked away by the criminals masquerading as the law, at the will of tyrants. So called leaders either complicit or incompetent; spineless, malicious, impotent.</p><p>And yet amidst this backdrop of our nation’s rapid descent, the dismantling of the systems we the people long toiled and struggled to build, life goes on. We go to our jobs, we eat our meals, at least those of us lucky enough to still possess the former and afford the latter, and life goes on.</p><p>But beneath the facade of normalcy, there is an undercurrent, a rising groundswell. Small acts of resistance. A word whispered. A vigilant eye. A community forged. Individually most of us are meek and timid, our power and influence limited, but it is through collective distributed action that the power of the people emerges. Little acts, in our own way, in our own time, lead to bigger acts together. Some may act in the light, others from the shadows. But all of us, driven by our own purpose, our own sense of justice, have the power to shape the world. At times we may act alone, or together, acts great or small, sometimes almost imperceptible, but acting nonetheless, we can take what little courage we may have in the moment, to kindle the courage in another, and light the beacons towards the dawn.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0dc96a9a36eb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Setting Up A Preview Site In XM Cloud With Vercel]]></title>
            <link>https://medium.com/programming-with-dly/setting-up-a-preview-site-in-xm-cloud-with-vercel-d4c55ec17761?source=rss-d0e7f6c2a314------2</link>
            <guid isPermaLink="false">https://medium.com/p/d4c55ec17761</guid>
            <category><![CDATA[preview]]></category>
            <category><![CDATA[sitecore-xm-cloud]]></category>
            <category><![CDATA[vercel]]></category>
            <category><![CDATA[nextjs]]></category>
            <dc:creator><![CDATA[Sophia Ly]]></dc:creator>
            <pubDate>Mon, 24 Mar 2025 21:43:48 GMT</pubDate>
            <atom:updated>2025-03-24T21:43:48.172Z</atom:updated>
            <content:encoded><![CDATA[<p>Often times stakeholders will want to be able to preview content before it gets published. These stakeholders do not need to have Sitecore access to view the content</p><h3>Create Vercel Site</h3><p>First you’ll need to create a new site in Vercel.</p><p>The reason we need a new one is because environment variables are tied to branches, so for the same branch you can’t set environment variables for both preview and non-preview.</p><p>NOTE: Configure the appropriate deployment protections to prevent external users from seeing this content, either via password, IP restriction, etc.</p><h4>Environment Variables</h4><p>For a preview site, you’ll need 3 environment variables.</p><pre>SITECORE_EDGE_CONTEXT_ID=<br>JSS_EDITING_SECRET=<br># Needed to proxy media/images to CM vis NextJS rewrite<br>SITECORE_API_HOST=<br># If not using MultiSite plugin<br>SITECORE_SITE_NAME=</pre><p>Most of the variables you can get from the XM Cloud Deploy app if you go to your [project] &gt; [environment] &gt; Developer Settings.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nyLnqLvsNUK2mxTJq4_Y-g.png" /></figure><p>For the SITECORE_API_HOST you can get it under the Details tab.</p><p>Be sure to prepend “https://” in because you need the full url, not just the domain name.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/635/1*fEFs5grQDy5Shm_Waqrd6w.png" /></figure><h4>Domain Name</h4><p>You’ll also need to configure a domain name for your site if you’re using the Multisite plugin.</p><h3>Sitecore Configuration</h3><p>There’s a few things you need to do on the Sitecore side.</p><h4>Config Patch</h4><p>The first thing you’ll need is a Sitecore config patch deployed to your authoring server.</p><pre>&lt;?xml version=&quot;1.0&quot;?&gt;<br>&lt;configuration xmlns:patch=&quot;http://www.sitecore.net/xmlconfig/&quot;<br>               xmlns:set=&quot;http://www.sitecore.net/xmlconfig/set/&quot;<br>               xmlns:role=&quot;http://www.sitecore.net/xmlconfig/role/&quot;<br>               xmlns:env=&quot;http://www.sitecore.net/xmlconfig/env/&quot;&gt;<br> &lt;sitecore&gt;<br>   &lt;pipelines&gt;<br>      &lt;httpRequestBegin&gt;<br>        &lt;processor type=&quot;Sitecore.Pipelines.HttpRequest.RequireAuthentication, Sitecore.Kernel&quot; resolve=&quot;true&quot;&gt;<br>          &lt;IgnoreRules hint=&quot;list:AddIgnoreRule&quot;&gt;<br>            &lt;prefix hint=&quot;media&quot;&gt;^/-/media/.*&lt;/prefix&gt;<br>          &lt;/IgnoreRules&gt;<br>        &lt;/processor&gt;<br>      &lt;/httpRequestBegin&gt;<br>    &lt;/pipelines&gt;<br> &lt;/sitecore&gt;<br>&lt;/configuration&gt;</pre><p>This will allow your media to be served from the media library unauthenticated on the CM. This is needed so that your preview can proxy and load images correctly.</p><h4>Site Definition Setup</h4><p>Next you’ll need to make sure that the preview domain you configured earlier is set up as a hostname in your site. You can do pipe separated to list out multiple domains.</p><h3>Code Setup</h3><p>There are a few pieces of code you’ll need to set up to ensure that assets work correctly.</p><h4>next.config.js</h4><p>First thing is you’ll need to update your next config.</p><p>The assetPrefix is set to the publicUrl because it is needed for editing since you’re on a different domain. However your public url is probably pointing to your live site. Since it’s only needed for editing, when we’re not running inside Sitecore, we can set it to undefined so it is relative to the current domain.</p><pre>const publicUrl = jssConfig.publicUrl;<br><br>// If not using Vercel, you&#39;ll need to distinguish between <br>// editing host vs normal.<br>const isVercel = process.env.VERCEL === &#39;1&#39;;<br><br>/**<br> * @type {import(&#39;next&#39;).NextConfig}<br> */<br>const nextConfig = {<br>  // Set assetPrefix to our public URL<br>  // In Vercel we need to set it to undefined for preview deployments to work<br>  // Since the preview url will be different from the public url<br>  assetPrefix: isVercel ? undefined : publicUrl,<br>  // Other settings<br>}</pre><h4>Making image urls relative</h4><p>Another thing is that sometimes, especially when querying via GraphQL, images come back with absolute urls rather than relative. Before rendering them we should fix them.</p><p>Helper function (simplified version) does that.</p><pre>/**<br> * To support preview site we normalize media urls to strip out the domain if it is coming from Sitecore.<br> */<br><br>export function normalizeImageUrl(src: string | undefined) {<br>  let newSrc = src;<br><br>  if (src) {<br>    const imageUrl = new URL(src, new URL(process.env.PUBLIC_URL as string));<br>    // If we&#39;re using the preview endpoint, replace the domain<br>    if (imageUrl?.pathname.startsWith(&#39;/-/media/&#39;)) {<br>      newSrc = src.replace(imageUrl.origin, &#39;&#39;);<br>    }<br>  }<br><br>  return newSrc;<br>}</pre><p>Note: It seems a recent change caused issues with proxying from localhost (HTTP) to HTTPS. For localhost, instead of removing the domain, you’ll want to replace it with process.env.SITECORE_API_HOST .</p><h3>Conclusion</h3><p>This should address the common issues getting a preview site up and running.</p><p>It’s been a while since I set things up from scratch so it’s possible some steps might be missed. Comment if something doesn’t seem to be working.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d4c55ec17761" width="1" height="1" alt=""><hr><p><a href="https://medium.com/programming-with-dly/setting-up-a-preview-site-in-xm-cloud-with-vercel-d4c55ec17761">Setting Up A Preview Site In XM Cloud With Vercel</a> was originally published in <a href="https://medium.com/programming-with-dly">Programming with DLy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Creating A Simple Dynamic Language Toggle In Sitecore XM Cloud (NextJS)]]></title>
            <link>https://medium.com/programming-with-dly/creating-a-simple-dynamic-language-toggle-in-sitecore-xm-cloud-nextjs-69e0a5fca038?source=rss-d0e7f6c2a314------2</link>
            <guid isPermaLink="false">https://medium.com/p/69e0a5fca038</guid>
            <category><![CDATA[sitecore-jss]]></category>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[sitecore-xm-cloud]]></category>
            <category><![CDATA[multilingual-websites]]></category>
            <dc:creator><![CDATA[Sophia Ly]]></dc:creator>
            <pubDate>Mon, 17 Mar 2025 11:53:12 GMT</pubDate>
            <atom:updated>2025-03-17T11:53:12.277Z</atom:updated>
            <content:encoded><![CDATA[<p>For multi-lingual sites one of the core features to include is a language toggle.</p><p>I often see some fairly complex implementations of this, but in many cases it’s overcomplicated without adding useful functionality.</p><p>For this exercise, we are implementing a language toggle that lets users change to a different language.</p><p>For some sites, not every page is translated to every language. In this case, we only want to show the languages that the current page is available in.</p><p>For the sake of simplicity, this will be un-styled and rendered as a list of links so you can take the concept and apply the desired UI.</p><p>This has no dependencies on any custom Sitecore data templates.</p><p>Let’s start.</p><h4>Getting The Data</h4><p>The first thing is we need to figure out what languages the current page is available in. To do that we use a GraphQL query.</p><pre>const LANGUAGE_QUERY = gql`<br>  query GetPageLanguages($path: String!, $language: String = &quot;en&quot;) {<br>    item(language: $language, path: $path) {<br>      languages {<br>        language {<br>          name<br>          nativeName<br>        }<br>      }<br>    }<br>  }<br>`;</pre><p>We use the name and nativeName properties, which correspond to the ISO code and the display name in that language.</p><p>We want to render the country’s flag as well, so we’ll create a data structure that also includes that country code.</p><pre>export interface ItemLanguage {<br>  isoCode: string;<br>  countryCode: string;<br>  nativeName: string;<br>}</pre><p>Now we need to create a PagePropsFactory plugin to populate this info. We’ll store an array of languages on the Sitecore Context.</p><pre><br>// Manual override, otherwise we&#39;ll try to parse from the language, i.e. &quot;es-MX&quot; would return &quot;MX&quot;<br>// This is mostly for when we use &quot;en&quot; instead of &quot;en-US&quot;, but can be expanded for other scenarios.<br>const CountryOverride: Record&lt;string, string | undefined&gt; = {<br>  en: &#39;US&#39;,<br>};<br><br>class PageLanguagesPlugin implements Plugin {<br>  order = 4;<br><br>  async exec(props: SitecorePageProps) {<br>    const graphqlClient = graphqlClientFactory();<br><br>    const result = await graphqlClient.request&lt;GetPageLanguagesType&gt;(LANGUAGE_QUERY, {<br>      path: props.layoutData.sitecore.route?.itemId ?? &#39;&#39;,<br>      language: props.layoutData.sitecore.context.language,<br>    });<br><br>    props.layoutData.sitecore.context.languages =<br>      result.item?.languages.map((x) =&gt; ({<br>        isoCode: x.language.name,<br>        countryCode: CountryOverride[x.language.name] ?? x.language.name.split(&#39;-&#39;)[1] ?? &#39;US&#39;,<br>        nativeName: x.language.nativeName,<br>      })) ?? [];<br><br>    return props;<br>  }<br>}</pre><p>This will work fine, but ideally we want intellisense for this property so we can extend the LayoutServiceContext type definition to include our new field. I like to put these things in a file called types/sitecore-jss-nextjs.d.ts.</p><pre>import { ItemLanguage } from &#39;src/lib/page-props-factory/plugins/page-languages&#39;;<br><br>declare module &#39;@sitecore-jss/sitecore-jss-nextjs&#39; {<br>  interface LayoutServiceContext {<br>    languages: ItemLanguage[] | null;<br>  }<br>}</pre><p>This is optional but I like having intellisense.</p><h4>Rendering The Component</h4><p>This is optional, but I like to have the flag of the country next to the language, so we’re going to use an npm package called country-flag-icons.</p><p>After that it’s pretty simple. We get the array of languages we populated earlier:</p><pre>const { sitecoreContext } = useSitecoreContext();<br><br>const languages = sitecoreContext.languages ?? [];</pre><p>We also need to import the flags to render them.</p><pre>import { US, MX, CA } from &#39;country-flag-icons/react/3x2&#39;;<br>const Flags = { US, MX, CA };<br><br>// Alternate method to dynamically include all flags, however it&#39;s recommended to only include<br>// necessary ones to reduce bundle size.<br>// import * as Flags from &#39;country-flag-icons/react/3x2&#39;;<br><br>type FlagNames = keyof typeof Flags;</pre><p>Then loop through each language and render it.</p><pre>        const CurrentFlag = Flags[item.countryCode as FlagNames];<br><br>        return (<br>          &lt;li key={item.isoCode}&gt;<br>            {/* We directly use NextLink here so we can change the `locale` and have NextJS handle it. */}<br>            &lt;NextLink href={{ pathname: pathname, query: query }} locale={item.isoCode}&gt;<br>              &lt;CurrentFlag width={20} /&gt;<br>              &lt;span className={isSelected ? &#39;font-bold&#39; : &#39;&#39;}&gt;{item?.nativeName}&lt;/span&gt;<br>            &lt;/NextLink&gt;<br>          &lt;/li&gt;<br>        );</pre><h4>Full Code</h4><p>src\lib\page-props-factory\plugins\page-languages.ts</p><pre>// Global<br>import { gql } from &#39;graphql-tag&#39;;<br><br>// Local<br>import { SitecorePageProps } from &#39;lib/page-props&#39;;<br>import { Plugin } from &#39;lib/page-props-factory&#39;;<br>import graphqlClientFactory from &#39;lib/graphql-client-factory&#39;;<br><br>// Manual override, otherwise we&#39;ll try to parse from the language, i.e. &quot;es-MX&quot; would return &quot;MX&quot;<br>// This is mostly for when we use &quot;en&quot; instead of &quot;en-US&quot;, but can be expanded for other scenarios.<br>const CountryOverride: Record&lt;string, string | undefined&gt; = {<br>  en: &#39;US&#39;,<br>};<br><br>class PageLanguagesPlugin implements Plugin {<br>  order = 4;<br><br>  async exec(props: SitecorePageProps) {<br>    const graphqlClient = graphqlClientFactory();<br><br>    const result = await graphqlClient.request&lt;GetPageLanguagesType&gt;(LANGUAGE_QUERY, {<br>      path: props.layoutData.sitecore.route?.itemId ?? &#39;&#39;,<br>      language: props.layoutData.sitecore.context.language,<br>    });<br><br>    props.layoutData.sitecore.context.languages =<br>      result.item?.languages.map((x) =&gt; ({<br>        isoCode: x.language.name,<br>        countryCode: CountryOverride[x.language.name] ?? x.language.name.split(&#39;-&#39;)[1] ?? &#39;US&#39;,<br>        nativeName: x.language.nativeName,<br>      })) ?? [];<br><br>    return props;<br>  }<br>}<br><br>export const pageLanguagesPlugin = new PageLanguagesPlugin();<br><br>export interface ItemLanguage {<br>  isoCode: string;<br>  countryCode: string;<br>  nativeName: string;<br>}<br><br>type GetPageLanguagesType = {<br>  item?: {<br>    languages: {<br>      language: {<br>        name: string;<br>        nativeName: string;<br>      };<br>    }[];<br>  };<br>};<br><br>// If TypeScript error, ensure GraphQL version matches what&#39;s used by JSS.<br>// Currently &quot;graphql&quot;: &quot;~16.9.0&quot;<br>const LANGUAGE_QUERY = gql`<br>  query GetPageLanguages($path: String!, $language: String = &quot;en&quot;) {<br>    item(language: $language, path: $path) {<br>      languages {<br>        language {<br>          name<br>          nativeName<br>        }<br>      }<br>    }<br>  }<br>`;</pre><p>LanguageSelector.tsx</p><pre><br>// Global<br>import { useRouter } from &#39;next/router&#39;;<br>import NextLink from &#39;next/link&#39;;<br>import { useSitecoreContext } from &#39;@sitecore-jss/sitecore-jss-nextjs&#39;;<br><br>// Local<br><br>import { useRealPathName } from &#39;lib/hooks/useRealPathName&#39;;<br><br>import { US, MX, CA } from &#39;country-flag-icons/react/3x2&#39;;<br>const Flags = { US, MX, CA };<br><br>// Alternate method to dynamically include all flags, however it&#39;s recommended to only include<br>// necessary ones to reduce bundle size.<br>// import * as Flags from &#39;country-flag-icons/react/3x2&#39;;<br><br>type FlagNames = keyof typeof Flags;<br><br>const LanguageSelector = () =&gt; {<br>  const router = useRouter();<br><br>  const locale = router.locale;<br><br>  // Don&#39;t use from router because that includes rewrites<br>  const pathname = useRealPathName();<br><br>  // Don&#39;t use `router.query` because that includes the wildcard JSS route &quot;path&quot;.<br>  const query = router.asPath.split(&#39;?&#39;)[1]?.split(&#39;#&#39;)[0];<br><br>  const { sitecoreContext } = useSitecoreContext();<br><br>  const languages = sitecoreContext.languages ?? [];<br><br>  const currentLanguage = languages.find((x) =&gt; x.isoCode === locale);<br><br>  /*<br>   * Rendering<br>   */<br><br>  return (<br>    &lt;ul&gt;<br>      {languages?.map((item) =&gt; {<br>        const isSelected = currentLanguage?.isoCode === item?.isoCode;<br><br>        const CurrentFlag = Flags[item.countryCode as FlagNames];<br><br>        return (<br>          &lt;li key={item.isoCode}&gt;<br>            {/* We directly use NextLink here so we can change the `locale` and have NextJS handle it. */}<br>            &lt;NextLink href={{ pathname: pathname, query: query }} locale={item.isoCode}&gt;<br>              &lt;CurrentFlag width={20} /&gt;<br>              &lt;span className={isSelected ? &#39;font-bold&#39; : &#39;&#39;}&gt;{item?.nativeName}&lt;/span&gt;<br>            &lt;/NextLink&gt;<br>          &lt;/li&gt;<br>        );<br>      })}<br>    &lt;/ul&gt;<br>  );<br>};<br><br>export default LanguageSelector;</pre><p>And a custom hook to help with getting the path name without rewrites interfering and causing a hydration warning.</p><p>useRealPathName.ts</p><pre>import { usePathname } from &#39;next/navigation&#39;;<br>import { pathExtractor } from &#39;../extract-path&#39;;<br><br>/**<br> * The path name on the server includes our rewritten path, which is different from on the client<br> * This causes a hydration error.  Use the pathExtractor to get the real path.<br> * @returns<br> */<br>export function useRealPathName() {<br>  const pathName = usePathname();<br>  const path = pathExtractor.extract({ path: pathName });<br>  return path;<br>}</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=69e0a5fca038" width="1" height="1" alt=""><hr><p><a href="https://medium.com/programming-with-dly/creating-a-simple-dynamic-language-toggle-in-sitecore-xm-cloud-nextjs-69e0a5fca038">Creating A Simple Dynamic Language Toggle In Sitecore XM Cloud (NextJS)</a> was originally published in <a href="https://medium.com/programming-with-dly">Programming with DLy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Deep-Linking Facets With Sitecore Search]]></title>
            <link>https://medium.com/programming-with-dly/deep-linking-facets-with-sitecore-search-749094bb3af6?source=rss-d0e7f6c2a314------2</link>
            <guid isPermaLink="false">https://medium.com/p/749094bb3af6</guid>
            <category><![CDATA[nextjs]]></category>
            <category><![CDATA[reactjs]]></category>
            <category><![CDATA[deeplink]]></category>
            <category><![CDATA[facet]]></category>
            <category><![CDATA[sitecore-search]]></category>
            <dc:creator><![CDATA[Sophia Ly]]></dc:creator>
            <pubDate>Mon, 17 Feb 2025 22:34:26 GMT</pubDate>
            <atom:updated>2025-02-17T22:51:54.447Z</atom:updated>
            <content:encoded><![CDATA[<h4>Out of the box, the components provided by Sitecore Search do not deep link into facets. In this article, we’ll create a React Hook to do just that.</h4><h4>Updating URL when facets change</h4><p>The first thing we need is to update the URL when facets change.</p><pre><br>/**<br> * Updates the url when selected facets change<br> */<br>const useEnsureFacetUrl = () =&gt; {<br>  const router = useRouter();<br><br>  // Get selected facets from Sitecore Search<br>  const selectedFacets = useSearchResultsSelectedFilters();<br><br>  // Convert facets into a url fragment<br>  const rawFacetUrlFragment = facetToUrlFragment(selectedFacets);<br><br>  // Call decodeURI to handle escaped characters, e.g. spaces.<br>  const decodedFacetUrlFragment = decodeURI(rawFacetUrlFragment);<br><br>  const currentHash = router.asPath.split(&#39;#&#39;)[1] ?? &#39;&#39;;<br><br>  useEffect(() =&gt; {<br>    // Only update if it&#39;s changed<br>    if (currentHash !== decodedFacetUrlFragment) {<br>      router.push(<br>        {<br>          pathname: window.location.pathname,<br>          // window.location.search includes the &#39;?&#39; if there is a querystring.<br>          // This caused extra &#39;?&#39; to be added each time.<br>          // If there is no querystring, there is no &#39;?&#39; so this issue wasn&#39;t caught earlier.<br>          query: window.location.search.replace(/^\?/, &#39;&#39;),<br>          hash: decodedFacetUrlFragment,<br>        },<br>        undefined,<br>        { scroll: false }<br>      );<br>    }<br>  }, [router, decodedFacetUrlFragment, currentHash]);<br>};</pre><p>We’ll define facetToUrlFragment shortly. It takes the list of facets and transforms it into a string that we can set as the URL hash.</p><p>Note: The reason we use window.location instead of router is because the router path will include any middleware rewrites which messes up the path. We could use router.asPath and parse out the parts we need, but it’s easier to use the window.location.</p><p>Now we define that facetUrlUrlFragmentfunction that we referenced above.</p><pre><br>// Optional prefix to indicate a hash parameter is a facet.  Recommended so as to not override other hash parameters<br>const FACET_PREFIX = &#39;f-&#39;;<br><br>function facetToUrlFragment(selectedFacets: SearchResultsSelectedFilters): string {<br>  if (!selectedFacets.length) {<br>    return &#39;&#39;;<br>  }<br>  const facets: Record&lt;string, string&gt; = {};<br>  selectedFacets.forEach((facet) =&gt; {<br>    const value = facet.valueLabel;<br>    if (!value) {<br>      return;<br>    }<br>    // Get the key, e.g. a facet of &quot;color&quot; would return &quot;f-color&quot;<br>    const key = FACET_PREFIX + facet.facetId;<br><br>    // If there are multiple selected facets with the same key, we join them by &#39;|&#39;<br>    // E.g. &quot;f-color=red|green|blue&quot;<br>    // If the key doesn&#39;t exist, define it as empty string<br>    if (!facets[key]) {<br>      facets[key] = &#39;&#39;;<br>    } else {<br>      // Otherwise add a pipe before we add the value<br>      facets[key] += &#39;|&#39;;<br>    }<br>    facets[key] += value;<br>  });<br>  const params = new URLSearchParams(facets);<br><br>  return params.toString();<br>}</pre><p>This will take something like:</p><pre>[<br>  { type: &#39;text&#39;, facetId: &#39;color&#39;, facetValueText: &#39;red&#39; },<br>  { type: &#39;text&#39;, facetId: &#39;color&#39;, facetValueText: &#39;green&#39; },<br>  { type: &#39;text&#39;, facetId: &#39;color&#39;, facetValueText: &#39;blue&#39; },<br>  { type: &#39;text&#39;, facetId: &#39;size&#39;, facetValueText: &#39;large&#39; },<br>];</pre><p>And return a string like f-color=red|green|blue&amp;f-size=large .</p><h4>Setting the initial state</h4><p>This handles half the equation, this will ensure the URL changes as the facet changes, but now we need to ensure that we set the initial state of the search interface based on what’s in the URL.</p><p>To do this, we create another hook, this time doing the logic in reverse, taking a string from the URL and returning a collection of facets.</p><pre><br>function useFacetsFromUrl(): SearchResultsSelectedFilters {<br>  const router = useRouter();<br><br>  const hash = router.asPath.split(&#39;#&#39;)[1] ?? &#39;&#39;;<br><br>  // Parse the hash parameter as if it were a querystring<br>  const hashAsQuery = new URLSearchParams(&#39;?&#39; + hash);<br><br>  const facets: SearchResultsSelectedFilters = [];<br><br>  for (const [key, value] of hashAsQuery.entries()) {<br>    // We only care about the parameters that are facets<br>    if (!key.startsWith(FACET_PREFIX)) {<br>      continue;<br>    }<br>    // Get the facet id from the key, e.g. &quot;f-color&quot; would return &quot;color&quot;<br>    const facetId = key.split(FACET_PREFIX)[1];<br><br>    // There could be multiple values, e.g. &quot;f-color=red|green|blue&quot;<br>    const valueArray = value.split(&#39;|&#39;);<br><br>    // Add each value to the list of facets to return<br>    valueArray.forEach((x) =&gt; {<br>      facets.push({ type: &#39;text&#39;, facetId, facetValueText: x });<br>    });<br>  }<br><br>  return facets;<br>}</pre><h4>Putting it all together</h4><p>Now we need to put this to use:</p><pre>  <br>  const router = useRouter();<br><br>  const keyphrase = (router.query.q as string) || defaultKeyphrase;<br><br>  const initialFacetsFromUrl = useFacetsFromUrl();<br>  <br>  const searchResults = useSearchResults&lt;ArticleModel, InitialState&gt;({<br>    state: {<br>      sortType: defaultSortType,<br>      page: defaultPage,<br>      itemsPerPage: defaultItemsPerPage,<br>      keyphrase: keyphrase,<br>      selectedFacets: initialFacetsFromUrl,<br>    },<br>  });<br><br>  useEnsureFacetUrl();</pre><p>We get the inital facets, then pass them to the initial state of useSearchResults. We then call useEnsureFacetUrl() to make sure the url stays updated.</p><p>We can actually combine these 2 into one hook:</p><pre>/**<br> * Ensures that updating facets updates the url, and returns the selected facets from url<br> * to pass to useSearchResults.<br> * @returns The current selected facets from the url<br> */<br>export const useFacetUrlSync = () =&gt; {<br>  useEnsureFacetUrl();<br>  return useFacetsFromUrl();<br>};</pre><p>And use it like so:</p><pre>  // Ensure that facets are synced with url and get initial facets.<br>  const initialFacetsFromUrl = useFacetUrlSync();<br><br>  const router = useRouter();<br>  <br>  const keyphrase = (router.query.q as string) || defaultKeyphrase;<br><br>  const searchResults = useSearchResults&lt;ArticleModel, InitialState&gt;({<br>    state: {<br>      sortType: defaultSortType,<br>      page: defaultPage,<br>      itemsPerPage: defaultItemsPerPage,<br>      keyphrase: keyphrase,<br>      selectedFacets: initialFacetsFromUrl,<br>    },<br>  });</pre><h4>Full Code for hooks</h4><p>Here’s the full file for the hooks, including the imports.</p><pre>// Global<br>import { useSearchResultsSelectedFilters } from &#39;@sitecore-search/react&#39;;<br><br>import { useRouter } from &#39;next/router&#39;;<br>import { useEffect } from &#39;react&#39;;<br><br>// Type isn&#39;t explicitly exported, so get the type from the return value.<br>type SearchResultsSelectedFilters = ReturnType&lt;typeof useSearchResultsSelectedFilters&gt;;<br><br>/**<br> * Ensures that updating facets updates the url, and returns the selected facets from url<br> * to pass to useSearchResults.<br> * @returns The current selected facets from the url<br> */<br>export const useFacetUrlSync = () =&gt; {<br>  useEnsureFacetUrl();<br>  return useFacetsFromUrl();<br>};<br><br>/**<br> * Updates the url when selected facets change<br> */<br>const useEnsureFacetUrl = () =&gt; {<br>  const router = useRouter();<br><br>  // Get selected facets from Sitecore Search<br>  const selectedFacets = useSearchResultsSelectedFilters();<br><br>  // Convert facets into a url fragment<br>  const rawFacetUrlFragment = facetToUrlFragment(selectedFacets);<br><br>  // Call decodeURI to handle escaped characters, e.g. spaces.<br>  const decodedFacetUrlFragment = decodeURI(rawFacetUrlFragment);<br><br>  const currentHash = router.asPath.split(&#39;#&#39;)[1] ?? &#39;&#39;;<br><br>  useEffect(() =&gt; {<br>    // Only update if it&#39;s changed<br>    if (currentHash !== decodedFacetUrlFragment) {<br>      router.push(<br>        {<br>          pathname: window.location.pathname,<br>          // window.location.search includes the &#39;?&#39; if there is a querystring.<br>          // This caused extra &#39;?&#39; to be added each time.<br>          // If there is no querystring, there is no &#39;?&#39; so this issue wasn&#39;t caught earlier.<br>          query: window.location.search.replace(/^\?/, &#39;&#39;),<br>          hash: decodedFacetUrlFragment,<br>        },<br>        undefined,<br>        { scroll: false }<br>      );<br>    }<br>  }, [router, decodedFacetUrlFragment, currentHash]);<br>};<br><br>// Optional prefix to indicate a hash parameter is a facet.  Recommended so as to not override other hash parameters<br>const FACET_PREFIX = &#39;f-&#39;;<br><br>function facetToUrlFragment(selectedFacets: SearchResultsSelectedFilters): string {<br>  if (!selectedFacets.length) {<br>    return &#39;&#39;;<br>  }<br>  const facets: Record&lt;string, string&gt; = {};<br>  selectedFacets.forEach((facet) =&gt; {<br>    // Depending on when it&#39;s called, sometimes it comes as facet.valueLabel, other times it&#39;s facet.facetValueText<br>    const value = facet.valueLabel; // ?? facet.facetValueText;<br>    if (!value) {<br>      return;<br>    }<br>    // Get the key, e.g. a facet of &quot;color&quot; would return &quot;f-color&quot;<br>    const key = FACET_PREFIX + facet.facetId;<br><br>    // If there are multiple selected facets with the same key, we join them by &#39;|&#39;<br>    // E.g. &quot;f-color=red|green|blue&quot;<br>    // If the key doesn&#39;t exist, define it as empty string<br>    if (!facets[key]) {<br>      facets[key] = &#39;&#39;;<br>    } else {<br>      // Otherwise add a pipe before we add the value<br>      facets[key] += &#39;|&#39;;<br>    }<br>    facets[key] += value;<br>  });<br>  const params = new URLSearchParams(facets);<br><br>  return params.toString();<br>}<br><br>function useFacetsFromUrl(): SearchResultsSelectedFilters {<br>  const router = useRouter();<br><br>  const hash = router.asPath.split(&#39;#&#39;)[1] ?? &#39;&#39;;<br><br>  // Parse the hash parameter as if it were a querystring<br>  const hashAsQuery = new URLSearchParams(&#39;?&#39; + hash);<br><br>  const facets: SearchResultsSelectedFilters = [];<br><br>  for (const [key, value] of hashAsQuery.entries()) {<br>    // We only care about the parameters that are facets<br>    if (!key.startsWith(FACET_PREFIX)) {<br>      continue;<br>    }<br>    // Get the facet id from the key, e.g. &quot;f-color&quot; would return &quot;color&quot;<br>    const facetId = key.split(FACET_PREFIX)[1];<br><br>    // There could be multiple values, e.g. &quot;f-color=red|green|blue&quot;<br>    const valueArray = value.split(&#39;|&#39;);<br><br>    // Add each value to the list of facets to return<br>    valueArray.forEach((x) =&gt; {<br>      facets.push({ type: &#39;text&#39;, facetId, facetValueText: x });<br>    });<br>  }<br><br>  return facets;<br>}</pre><p>We define InitialState as:</p><pre>type InitialState = SearchResultsInitialState&lt;&#39;itemsPerPage&#39; | &#39;keyphrase&#39; | &#39;page&#39; | &#39;sortType&#39; | &#39;selectedFacets&#39;&gt;;</pre><p>The use the search results:</p><pre>  // Ensure that facets are synced with url and get initial facets.<br>  const initialFacetsFromUrl = useFacetUrlSync();<br><br>  const router = useRouter();<br>  <br>  const keyphrase = (router.query.q as string) || defaultKeyphrase;<br><br>  const searchResults = useSearchResults&lt;ArticleModel, InitialState&gt;({<br>    config: {<br>      // Not strictly needed, but depending on how facets are configured, it can ensure the correct type is used.<br>      defaultFacetType: &#39;text&#39;,<br>    },<br>    state: {<br>      sortType: defaultSortType,<br>      page: defaultPage,<br>      itemsPerPage: defaultItemsPerPage,<br>      keyphrase: keyphrase,<br>      selectedFacets: initialFacetsFromUrl,<br>    },<br>  });</pre><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=749094bb3af6" width="1" height="1" alt=""><hr><p><a href="https://medium.com/programming-with-dly/deep-linking-facets-with-sitecore-search-749094bb3af6">Deep-Linking Facets With Sitecore Search</a> was originally published in <a href="https://medium.com/programming-with-dly">Programming with DLy</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Vulnerability Is An Emotional Workout]]></title>
            <link>https://medium.com/@SophiaCactuar/vulnerability-is-an-emotional-workout-791b3da5d249?source=rss-d0e7f6c2a314------2</link>
            <guid isPermaLink="false">https://medium.com/p/791b3da5d249</guid>
            <category><![CDATA[friendship]]></category>
            <category><![CDATA[connection]]></category>
            <category><![CDATA[trust]]></category>
            <category><![CDATA[vulnerability]]></category>
            <category><![CDATA[emotions]]></category>
            <dc:creator><![CDATA[Sophia Ly]]></dc:creator>
            <pubDate>Sun, 17 Nov 2019 13:56:57 GMT</pubDate>
            <atom:updated>2019-11-17T13:56:57.712Z</atom:updated>
            <content:encoded><![CDATA[<h4>How being an open book is not the same as being vulnerable</h4><figure><img alt="four persons sitting on concrete bench" src="https://cdn-images-1.medium.com/max/1024/1*Xv0NintXvnI2flJjSBbIQw.jpeg" /><figcaption>Photo by <a href="https://unsplash.com/@katekalvach?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Kate Kalvach</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></figcaption></figure><p>This is a nuance of vulnerability that took me a while to really understand. Everyone talks about vulnerability as opening up, being willing to share. But it’s more than that, it’s also about knowing that there’s a possibility you might get hurt; that the thing you’re sharing doesn’t resonate with the other person.</p><p>It really clicked for me when I was talking to someone who had no issues sharing things that many people consider very personal or private, and based on my previous understanding of vulnerability should have been a very vulnerable act. But it wasn’t. I didn’t feel the trust, and later when I asked something that was more personal to them, they didn’t really want to answer and sidestepped the question. (I want to note that there is absolutely nothing wrong with that; a person chooses to be vulnerable, you cannot force that on someone.)</p><p>I now think of vulnerability as an emotional workout. Just like there’s a difference between sitting up, and doing a sit-up, there’s a difference between openly sharing everything, and sharing something that’s important to you, where you’re unsure of whether the other person will accept it, or reject or be indifferent to it. The key is, you actually have to care about the outcome. If it makes no difference to you how the other person reacts, there is no vulnerability. It’s not about what you share, it’s about what it means to you.</p><p>To continue the workout analogy, the muscles that you are building is trust. Trust is both the prerequisite, and the outcome of vulnerability; you have to use your muscles to build your muscles. And while you want to push your past the point where it’s easy, you still need to be aware of the current limits. There’s a difference between being sore from a good workout, and injuring yourself. If your opening up fails to reach someone, you should feel disappointed, or sad, but you should not be completely devastated. It should be something you recover from in hours or days.</p><p>Stepping away from the analogy (analogies have their limits!), being an open book can be just as much of a shield as keeping everything to yourself. Being an open book doesn’t require trust. The same goes for the “I don’t care what other people think”. There’s a line from the show Scrubs that I think exemplifies the distinction.</p><blockquote>JD: Dude, it was one second of doubt. Since when do you care what anyone thinks?<br>Turk: I don’t, I care what you think.</blockquote><p>That, I think, is the essence of what vulnerability means.</p><p>We shouldn’t be vulnerable all the time, we can’t. But without it, we aren’t able to build emotional trust in people. I say emotional trust, because you can trust someone professionally, to be able to do their job well, but that’s not the same kind of trust, although there can be an overlap. At the end of the day, if we want to build meaningful trusting connections with people, we will need to allow ourselves to be disappointed, or hurt. To be vulnerable.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=791b3da5d249" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Why I Support the Right to an Abortion, and What to do About it.]]></title>
            <link>https://medium.com/@SophiaCactuar/why-i-support-the-right-to-an-abortion-and-what-to-do-about-it-5acd48db2d67?source=rss-d0e7f6c2a314------2</link>
            <guid isPermaLink="false">https://medium.com/p/5acd48db2d67</guid>
            <category><![CDATA[abortion]]></category>
            <dc:creator><![CDATA[Sophia Ly]]></dc:creator>
            <pubDate>Sat, 18 May 2019 04:58:17 GMT</pubDate>
            <atom:updated>2019-05-18T04:58:17.332Z</atom:updated>
            <content:encoded><![CDATA[<p>I believe in a person’s right to reproductive healthcare and bodily autonomy. I believe that having a potential child-bearer be able to decide when and whether to have kids is a necessary step towards social and economical equality.</p><p>I’m going to skip over the obvious debate point of when “life” begins because it’s a question of philosophy and semantics that I consider it an intractable question. Some point to the scientific definition, which In my opinion is irrelevant here because the question being asked is philosophical in nature. It’s about as relevant as treating a tomato as a fruit when discussing what to put in a fruit salad.</p><p>With that being the case, any meaningful framework of discussion of the morality of abortions will have stand regardless of one’s position on when life begins. The framework for discussion that makes the most sense to me is the one of bodily autonomy. Now, it’s not immediately obvious (at least it wasn’t immediately obvious to me) that the notion of bodily autonomy supports the right to an abortion, especially if you hold the belief that a fetus is a full human.</p><p>What clicked for me is realizing what we are asking someone to do when we ask them to carry a pregnancy to term against their will. We are asking for use of their bodily organs and endure anywhere from moderate to extreme discomfort and pain to support another. There are countless people who choose to go through this process willingly, but they key is that it is their choice. In other circumstances we cannot force someone to use their bodies to save another. We cannot force somebody to donate a part of a liver, even though it can grow back and heal back to normal. We cannot even force someone to donate blood, even though it is minimally invasive. We cannot force people to do these things even if a family member’s life depended on it.</p><p>A couple things of note. Supporting the right to an abortion is not the same as encouraging abortions. Much like supporting the right to burn the flag is not the same as encouraging the burning of the flag. Continuing that analogy, it is not my place to determine whether the reason someone chooses to have an abortion is valid, just like it would not be my place determine whether someone had a good reason for burning the flag. Whether or not I agree with the reason is irrelevant in both cases.</p><p>Secondly, I believe in reducing the frequency of abortions. I do not consider it a good thing, but I believe the not having the option to be worse. I believe in improving access to and education around birth control, as well as research into improving the effectiveness and reducing the side effects of various birth control methods. I believe in promoting safe sex, both because focusing on abstinence has proven time and time again to be ineffective, and because I believe that sex can and should be about pleasure and/or intimacy.</p><p>I have intentionally avoided specifying gender because I have a number and trans male friends, some of whom have mentioned how they felt excluded in a topic that was relevant to them, as they are men who can get pregnant, and limiting the discussion to women would exclude them. I could have specified people with a uterus but that felt like an implied qualifier for the topic at hand.</p><p>Regarding the recent bans in several states, it is clear that there is a coordinated effort to bring the case to the Supreme Court, coming shortly after placing 2 conservative leaning justices. It is unclear how that would play out. The opinions I see currently floating around suggest it is unlikely that Roe v. Wade will be directly struck down but that a more likely scenario would be further weakening the protections it provides.</p><p>Directly influencing the courts by the public will likely have limited impact (but likely more than nothing). The other route that seems to be more plausible is introducing new legislation. While Supreme Court justices are lifetime appointments that are less impacted by the court of public opinion, their role is to interpret the law. It is congress’s role to create laws, and it is there that the people can have greater influence through public pressure and votes. Senators <a href="https://medium.com/@teamwarren/congressional-action-to-protect-choice-aaf94ed25fb5">Warren</a> and <a href="https://medium.com/@SenGillibrand/why-i-went-to-the-frontlines-of-the-assault-on-abortion-rights-and-what-ill-do-about-it-d2c14c30e1e7">Gillibrand</a> have already made proposals for codifying court interpretation from Roe v. Wade into concrete laws which will be more powerful than simply legal precedence. I believe the greatest impact to be made by common citizens is to support elected representatives who back such efforts and to pressure those who oppose.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5acd48db2d67" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Labels — Identity vs Descriptor]]></title>
            <link>https://medium.com/@SophiaCactuar/labels-identity-vs-descriptor-5d9436d344dd?source=rss-d0e7f6c2a314------2</link>
            <guid isPermaLink="false">https://medium.com/p/5d9436d344dd</guid>
            <category><![CDATA[self-reflection]]></category>
            <category><![CDATA[self]]></category>
            <category><![CDATA[identity]]></category>
            <category><![CDATA[people]]></category>
            <dc:creator><![CDATA[Sophia Ly]]></dc:creator>
            <pubDate>Thu, 03 May 2018 02:54:16 GMT</pubDate>
            <atom:updated>2018-05-03T02:54:16.647Z</atom:updated>
            <content:encoded><![CDATA[<h4>The difference between who we are, and what we do</h4><p>There is a subtle but important difference when we use take on a label as an identity vs when it is a mere descriptor. The same label may be an identity for one person, but merely a descriptor for another. For some people, their race, culture, profession, political affiliation, or even fandom represents a part of their identity, part of who they are. For others it is merely a descriptor, a statement of fact, but nothing more.</p><p>When we are talking with people about a topic related to something they consider part of their identity, they are likely to have strong feelings and opinions on the topic. This doesn’t mean they are irrational about the topic or can’t be reasoned with. Nor is having an identity label a bad thing. It gives a certain perspective and can be an important tool both externally for unifying others similar to us, as well as a tool for gaining insight into who we are as a person.</p><p>For myself, being polyamorous is an identity for me. It is an important part of myself that I’ve only truly started to explore in recent years, but looking back at my life it has been a part of myself that wasn’t really explored or expressed. Another part of my identity being a shy extrovert. I love going out with friends, and it’s usually energizing rather than draining, but it’s also something that I have trouble with initiating, or even deciding to do. In both of theses cases, exploring that part of myself helped me to understand myself better.</p><p>Something I don’t consider an identity label for me is veganism. Even though I try to adhere to a vegan/plant-based diet most of the time, it isn’t a core part of who I am. It’s something I do, not something I am. In a similar vein, I believe in feminism and agree with an occasionally promote most feminist ideals, I do not consider it an identity, and would not call myself a feminist, just like I would not call myself a democrat despite my voting record and being registered as such.</p><p>An identity is more than just a belief, and isn’t something taken on lightly. Our identities can shift over time, but they aren’t superficial. Changes often involve deep soul searching. They can help us an others understand who we are as people.</p><p>What do you consider to be part of your identity?</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5d9436d344dd" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A Year Of Love]]></title>
            <link>https://medium.com/poly-me/a-year-of-love-c3fa33b5a8ee?source=rss-d0e7f6c2a314------2</link>
            <guid isPermaLink="false">https://medium.com/p/c3fa33b5a8ee</guid>
            <category><![CDATA[2017]]></category>
            <category><![CDATA[polyamory]]></category>
            <category><![CDATA[relationships]]></category>
            <category><![CDATA[love]]></category>
            <dc:creator><![CDATA[Sophia Ly]]></dc:creator>
            <pubDate>Sun, 31 Dec 2017 23:37:37 GMT</pubDate>
            <atom:updated>2017-12-31T23:59:11.503Z</atom:updated>
            <content:encoded><![CDATA[<p>National-wide and across the globe, 2017 has been a tumultuous year. Between the ecological disasters induced by global climate change, and the systemic attempts to destabilize our national government, there is a lot of fear and cynicism around the world. At the same time, there is also hope and resistance to the entropy that seeks to envelop us. And in my personal life, there is also love.</p><p>Almost exactly 1 year ago, around 1:00AM on Jan 1, 2017, I picked up my now girlfriend from a party, to go on our first date at Perkins (it was the only place nearby that was open). She told me I only get 4 dates (Just between you and me, I think she was wrong 😜)</p><p>In the year since that date, we’ve had the usual ups and downs, miscommunications and misunderstandings. But through it all, we’ve found love, and joy, and compassion.</p><p>One of the interesting things I’ve noticed in my relationships, with my girlfriend and my wife is how they observe <a href="https://www.gottman.com/blog/the-magic-relationship-ratio-according-science/">Gottman’s ratio for healthy long term relationships</a> differently. From his longitudinal study on relationship longevity he discovered the ratio between positive and negative interactions for a couple to stay together to be approximately 5:1. While I haven’t sat down and actually tallied up our positive and negative interactions, based off a rough guesstimate I’d say we’re pretty well in the green on all accounts. The interesting aspect is that with my wife there are fewer interactions, both positive and negative, and with my girlfriend there are more of each. But as the study showed, the important part isn’t the number of interactions, but the ratio.</p><p>Our kids also get along well together, which is fun. They even occasionally call each other sisters and brothers which is super cute. It’s also nice that we are all openly polyamorous so we don’t have to keep it from the kids out of fear that they’ll accidentally “out” us. As far as they’re concerned, it’s just normal and there isn’t anything weird or noteworthy about it. When they get old enough to ask about relationships of the romantic variety we’ll probably talk about it more formally but for now we’ll just let them take things in and not make a big deal out of it.</p><p>My relationship with my wife is as strong as ever, and I’m grateful for her constant presence in my life. Her relationship with her boyfriend is also going well. One of the most amusing incidents this year is when we went to a non-monogamy event where on my left were my wife and her boyfriend, and on my right were my girlfriend, her boyfriend, and his girlfriend. Poly relationships can be interesting like that 😄. Of course I would be remiss if I didn’t mention my other partner. While we don’t see each other very often and our lives aren’t as intertwined, we have deepened our relationship over the year, and I told her I love her for the first time this year.</p><p>All in all, 2017 has been one of my best years personally. I know that isn’t the case for everyone out there, it’s been a rough year for many. For those of you who have had a rough 2017, I hope that 2018 brings about a change in fortunes. And for those who have had a good year, may the next continue to bring you joy.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c3fa33b5a8ee" width="1" height="1" alt=""><hr><p><a href="https://medium.com/poly-me/a-year-of-love-c3fa33b5a8ee">A Year Of Love</a> was originally published in <a href="https://medium.com/poly-me">Poly &amp; Me</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Jumble of thoughts]]></title>
            <link>https://medium.com/@SophiaCactuar/jumble-of-thoughts-2f33c28c193e?source=rss-d0e7f6c2a314------2</link>
            <guid isPermaLink="false">https://medium.com/p/2f33c28c193e</guid>
            <category><![CDATA[hate-groups]]></category>
            <category><![CDATA[empathy]]></category>
            <category><![CDATA[extremism]]></category>
            <category><![CDATA[politics]]></category>
            <category><![CDATA[psychology]]></category>
            <dc:creator><![CDATA[Sophia Ly]]></dc:creator>
            <pubDate>Fri, 18 Aug 2017 03:20:16 GMT</pubDate>
            <atom:updated>2017-08-18T03:20:16.285Z</atom:updated>
            <content:encoded><![CDATA[<p>I’m still not able to fully flesh out my thoughts in writing yet, but here are a couple high level thoughts I have:</p><p>Shaming is a psychologically ineffective tool at affecting change.* It doesn’t work for alcoholics, it doesn’t work on criminals, and it doesn’t work on extremists of any stripe, domestic or foreign.</p><p>Just last year I thought that labeling the “alt-right” as Nazis was hyperbole and an invocation of <a href="https://en.wikipedia.org/wiki/Godwin%27s_law">Godwin’s Law</a>. I wish I could say I was right, but it’s become clear that I was mistaken.</p><p>The goal should be to destroy the ideology, not the people. Many are misguided and need a severe reality check and history lessons.</p><p>Empathy and understanding are not the same as acceptance. A teacher can understand how a student may have come to a certain conclusion but still unequivocally demonstrate how that conclusion is incorrect.</p><p>Self care is important. For many like myself, being overwhelmed leads to inaction.</p><p>There’s a big difference between “shouldn’t have to” and “don’t have to”. People shouldn’t have to work two full time jobs, but some people do have to. It’s the difference between what reality is currently and what reality should be.</p><p>Different people need different messaging and approaches to change their minds. The broadcast nature of the internet makes it difficult to tailor a message to a particular audience.</p><p>*There’s various definitions of “shame” that are used, and so there’s differing results in psychological studies. The definition I use is the one that Brene Brown uses, which is that shame is when the person is seen as fundamentally flawed as opposed to that person’s actions being unacceptable.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2f33c28c193e" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>