<?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 Lyndon Shi on Medium]]></title>
        <description><![CDATA[Stories by Lyndon Shi on Medium]]></description>
        <link>https://medium.com/@shilyndon?source=rss-96b8b31ff0c2------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/2*jsJ4LzWwRejyedf3FXmCWw.jpeg</url>
            <title>Stories by Lyndon Shi on Medium</title>
            <link>https://medium.com/@shilyndon?source=rss-96b8b31ff0c2------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Fri, 10 Apr 2026 17:08:11 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@shilyndon/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[Using Sortable with Svelte + Tauri]]></title>
            <link>https://medium.com/@shilyndon/sortable-svelte-tauri-getting-a-sortable-list-right-in-a-cross-platform-app-f9727d1547db?source=rss-96b8b31ff0c2------2</link>
            <guid isPermaLink="false">https://medium.com/p/f9727d1547db</guid>
            <dc:creator><![CDATA[Lyndon Shi]]></dc:creator>
            <pubDate>Sun, 18 Jan 2026 19:38:14 GMT</pubDate>
            <atom:updated>2026-01-18T19:47:12.488Z</atom:updated>
            <content:encoded><![CDATA[<blockquote>This is a condensed version of the <a href="https://lynshi.github.io/posts/sortable_svelte_and_tauri/">write-up on my personal site</a>.</blockquote><p>I recently struggled with getting <a href="https://github.com/SortableJS/Sortable">Sortable</a> to work in my app, as the combination of <a href="https://svelte.dev/">Svelte</a> and <a href="https://v2.tauri.app/">Tauri</a> created integration complexities. Since I didn’t find a resource that explained how to use all three together and consequently spent a lot of time debugging, here’s a one-stop post for how I managed to get it working.</p><h3>Wrapping Sortable in an attachment</h3><p>I created a helper function, sortableList, that returns an <a href="https://svelte.dev/docs/svelte/@attach">attachment</a> creating a Sortable given the input options. Using it is as easy as attaching the function call to a list. I also wrote another helper function to return the order of items after a reordering.</p><pre>import Sortable from &quot;sortablejs&quot;;<br>import type { Attachment } from &quot;svelte/attachments&quot;;<br><br>export const sortableList = (options: Sortable.Options): Attachment =&gt; {<br>  return (element: Element) =&gt; {<br>    const sortable = Sortable.create(element as HTMLElement, options);<br>    return () =&gt; {<br>      sortable.destroy();<br>    };<br>  };<br>};<br><br>export function reorder&lt;T&gt;(<br>  array: T[],<br>  evt: Sortable.SortableEvent,<br>): T[] | null {<br>  const newArray = [...$state.snapshot(array)] as T[];<br>  const { oldIndex, newIndex } = evt;<br><br>  if (oldIndex === undefined || newIndex === undefined) {<br>    return null;<br>  }<br>  if (newIndex === oldIndex) {<br>    return null;<br>  }<br><br>  const target = newArray[oldIndex];<br>  const increment = newIndex &lt; oldIndex ? -1 : 1;<br><br>  for (let k = oldIndex; k !== newIndex; k += increment) {<br>    newArray[k] = newArray[k + increment];<br>  }<br>  newArray[newIndex] = target;<br>  return newArray;<br>}</pre><pre>&lt;script&gt;<br>  import { reorder, sortableList } from &quot;$lib/sortable.svelte&quot;;<br><br>  // Your list of things<br>  const items = [<br>    {<br>      id: &quot;item-id&quot;,<br>      text: &quot;item-text&quot;,<br>    }<br>  ];<br><br>  const sortableOptions = {<br>    onUpdate(event: any) {<br>      const newList = reorder(groups, event);<br>      if (newList == null) {<br>        return;<br>      }<br>  <br>      // Placeholder for updating the order of items.<br>      updateOrdering(newList);<br>    }<br>  }<br>&lt;/script&gt;<br><br>{#key items}<br>  &lt;ul {@attach sortableList(sortableOptions)}&gt;<br>    {#each items as item (item.id)}<br>      &lt;li&gt;{item.text}&lt;/li&gt;<br>    {/each}<br>  &lt;/ul&gt;<br>{/key}</pre><p>I found that it was crucial to wrap the list in a #key block so that it’s recreated every time you reorder items. Otherwise, the Svelte and Sortable states get out of sync, and your list won’t be displayed in the expected order.</p><h3>Disabling application window drag-and-drop</h3><p>On a desktop app, you’re often able to drag-and-drop items directly into the application window. This functionality overrides drag-and-drop within the webview in the Tauri app. To allow drag-and-drop of the Sortable items, you need to disable the application-level drag-and-drop in tauri.conf.json via the dragDropEnabled setting.</p><pre>{<br>    &quot;app&quot;: {<br>      &quot;windows&quot;: [<br>        {<br>          &quot;dragDropEnabled&quot;: false<br>        }<br>      ],<br>    }<br>}</pre><h3>Disabling the HTML5 DND implementation</h3><p>Sortable has two implementations for its drag-and-drop functionality based on whether the browser supports HTML5. After some experimentation, I realized that the HTML5 DND implementation does not work in Tauri. Specifically, I noticed that I would often be unable to reorder items consecutively — after moving one item, I had to click anywhere else on the view to “reset” and be able to move the next item. To fix this problem, force Sortable to use the non-HTML5 implementation with the <a href="https://github.com/SortableJS/Sortable?tab=readme-ov-file#forcefallback-option">option</a> forceFallback.</p><pre>const sortable = Sortable.create(el, {<br>  ...options,<br>  forceFallback: true,<br>});</pre><p>Many thanks to these resources which proved quite helpful while I was attempting this:</p><ul><li><a href="https://dev.to/jdgamble555/svelte-5-and-sortablejs-5h6j">https://dev.to/jdgamble555/svelte-5-and-sortablejs-5h6j</a></li><li><a href="https://www.reddit.com/r/tauri/comments/1hg85k7/using_vue_3_dragable_components_why_do_i_need_to/">https://www.reddit.com/r/tauri/comments/1hg85k7/using_vue_3_dragable_components_why_do_i_need_to/</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=f9727d1547db" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Purging Azure CDN With GitHub Actions]]></title>
            <link>https://medium.com/@shilyndon/purging-azure-cdn-with-github-actions-1c18e2adaf18?source=rss-96b8b31ff0c2------2</link>
            <guid isPermaLink="false">https://medium.com/p/1c18e2adaf18</guid>
            <category><![CDATA[github]]></category>
            <category><![CDATA[azure]]></category>
            <category><![CDATA[programming]]></category>
            <category><![CDATA[github-actions]]></category>
            <dc:creator><![CDATA[Lyndon Shi]]></dc:creator>
            <pubDate>Wed, 01 Jan 2020 04:13:58 GMT</pubDate>
            <atom:updated>2020-02-17T02:54:11.378Z</atom:updated>
            <content:encoded><![CDATA[<h3>Programmatically Purging Azure CDN With GitHub Actions</h3><p>When you use Azure CDN to deliver content, you may need to purge your endpoint so that changes you make are sent to your users, as <a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-manage-expiration-of-cloud-service-content">files are cached in the Azure CDN until their time-to-live (TTL) expires</a>. If you don’t set a TTL for your files, Azure automatically sets a TTL of 7 days. Even if you set a lower TTL, your updates may not coincide with the cache expiration.</p><p>For example, my personal website uses Azure CDN and is updated on every push I make to GitHub. It’s tough to set a good caching rule for this because my changes are unpredictable. When I’m working on my site, I might push several times a day, but I can also go weeks without modifying my website.</p><p>While it is possible to <a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-purge-endpoint">purge an endpoint through the Azure portal</a>, I wanted to automate this process. Fortunately, this is possible with <a href="https://github.com/features/actions">GitHub Actions</a>.</p><h4>Creating a Service Principal</h4><p>An <a href="https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?toc=%2Fazure%2Fazure-resource-manager%2Ftoc.json&amp;view=azure-cli-latest">Azure service principal</a> is “an identity created for use with applications, hosted services, and automated tools to access Azure resources”. It is recommended, for better security, to use service principals with automated tools, since access is restricted by roles assigned to the service principal. In this section, I’ll discuss how to create a service principal and give it the <a href="https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#cdn-endpoint-contributor">CDN Endpoint Contributor</a> role. To get started, you’ll need the <a href="https://docs.microsoft.com/en-us/cli/azure/get-started-with-azure-cli?view=azure-cli-latest">Azure CLI</a>.</p><p>To create a new service principal, enter the following command in your terminal, making sure to copy the correct&lt;subscription_id&gt; and &lt;resource_group_name&gt; from your Azure portal.</p><pre>az ad sp create-for-rbac -n &quot;&lt;name_of_service_principal&gt;&quot; --role &quot;CDN Endpoint Contributor&quot; --sdk-auth --scopes /subscriptions/&lt;subscription_id&gt;/resourceGroups/&lt;resource_group_name&gt;</pre><p>The output will look similar to the following; be sure to copy and save it somewhere safe, as you will not be able to retrieve the clientSecret in the future.</p><pre>{                                                                                 &quot;clientId&quot;: &quot;&lt;GUID&gt;&quot;,                             <br>&quot;clientSecret&quot;: &quot;&lt;GUID&gt;&quot;,                         <br>&quot;subscriptionId&quot;: &quot;&lt;GUID&gt;&quot;,                       <br>&quot;tenantId&quot;: &quot;&lt;GUID&gt;&quot;<br>}</pre><p>In the <a href="https://docs.microsoft.com/en-us/cli/azure/ad/sp?view=azure-cli-latest#az-ad-sp-create">command</a>, we are creating a service principal and assigning it a role of “CDN Endpoint Contributor”. This allows the service principal to manage CDN endpoints. The --sdk-auth flag causes the output to be correctly formatted for use in the GitHub Action; without it, you will get the following error when you copy the output to GitHub.</p><pre>Error: Not all values are present in the creds object. Ensure clientId, clientSecret, tenantId and subscriptionId are supplied.</pre><h4>Adding GitHub Secrets</h4><p>Add the following secrets (encrypted environment variables) to your GitHub repository:</p><ol><li>AZURE_CREDENTIALS: paste the output from the az ad sp create-for-rbac command.</li><li>AZURE_CDN_ENDPOINT: the name of your Azure CDN endpoint.</li><li>AZURE_CDN_PROFILE_NAME: the name of your Azure CDN profile.</li><li>AZURE_RESOURCE_GROUP: the name of the resource group containing the Azure CDN profile.</li></ol><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*RwM3IOTXVjwtQCcMkDWW-A.png" /><figcaption>Find the Secrets tab in the Settings page of your GitHub repository to begin adding secret environment variables.</figcaption></figure><h4>Writing the GitHub Actions Workflow File</h4><p>Now that you have a service principal and GitHub secrets, all that remains is to write the GitHub Action. There are two essential steps in the workflow: 1) logging in to Azure, and 2) purging the CDN. To start, go to your repository on GitHub and select the “Actions” tab. Click “New workflow”, then choose “Set up a workflow yourself” or the appropriate starter for your needs.</p><p>The following is the bare minimum required for this workflow.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cbf65213272d4d3d8ec2b8744103802a/href">https://medium.com/media/cbf65213272d4d3d8ec2b8744103802a/href</a></iframe><p>The Azure service principal login step logs in to Azure CLI with the credentials you added to your GitHub secret. The Purge CDN step purges the CDN, so your updates should occur between these two steps. In the Purge CDN step, --content-paths &quot;/*&quot; purges all content. --no-wait means the script does not wait for the purge operation to finish before continuing; this flag is optional. You can read more about the az cdn endpoint purge command <a href="https://docs.microsoft.com/en-us/cli/azure/cdn/endpoint?view=azure-cli-latest#az-cdn-endpoint-purge">here</a>.</p><p>That’s all there is to it, happy coding!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=1c18e2adaf18" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Automating Deployment of a (Gatsby) Static Website on Azure Storage with GitHub Actions]]></title>
            <link>https://medium.com/@shilyndon/automating-deployment-of-a-gatsby-static-website-on-azure-storage-with-github-actions-c81a63b32a9a?source=rss-96b8b31ff0c2------2</link>
            <guid isPermaLink="false">https://medium.com/p/c81a63b32a9a</guid>
            <category><![CDATA[gatsbyjs]]></category>
            <category><![CDATA[github-actions]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[azure]]></category>
            <category><![CDATA[personal-website]]></category>
            <dc:creator><![CDATA[Lyndon Shi]]></dc:creator>
            <pubDate>Sun, 29 Dec 2019 19:46:39 GMT</pubDate>
            <atom:updated>2020-01-01T04:15:51.686Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*HbQtNXVua60jpqXmAgM4Xg.png" /><figcaption>View my site at <a href="https://me.lynshi.com/">me.lynshi.com</a>!</figcaption></figure><p>I host my own static website on Azure Storage, and I got tired of running a script to upload my content every time I made a change to my website. After some Googling, I discovered <a href="https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/">GitHub Actions</a>, made public about a month ago, which lets users automate how they build, test, and deploy their projects. Interestingly, though I found great guides for <a href="https://medium.com/faun/automating-your-deployment-using-gitlab-azure-storage-static-website-hosting-75c767b2569f">automating static website deployment to Azure using GitLab</a> and <a href="https://nehalist.io/building-and-deploying-gatsby-sites-with-github-actions/">building and deploying Gatsby sites with GitHub Actions</a>, there wasn’t one guide with everything I needed, and I had to cobble pieces together from different sources. This is an attempt to rectify that.</p><p>This post assumes your static website is already up and running on Azure Storage. If not, the <a href="https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website">Azure documentation on static website hosting</a> was enough for me to get started.</p><h4>Setup</h4><p>To begin, go to the Azure Storage account with your static website content, and find the setting “Access keys”. Copy either one of the connection strings; this is needed to upload content to Azure via GitHub actions.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*nvKSMIPWQYfbcgUenfgJpg.png" /><figcaption>To find the connection string, go to “Access keys”.</figcaption></figure><p>Now, open your personal website repository on GitHub. Go to the settings tab and select “Secrets” in the left panel. Click “Add a new secret”, name it “AZURE_STORAGE_CONNECTION_STRING” (you can use a different name as long as you update the GitHub Actions .yml accordingly), and paste the connection string value obtained from Azure into the “Value” area.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*MlelyBz-fvwnVyp99E6dpQ.png" /><figcaption>Add the Azure connection string to your secrets in your personal website’s GitHub repository.</figcaption></figure><h4>Workflow Script</h4><p>To build your Action, go to the “Actions” tab in your repository. Select “New workflow” and use the Node.js starter.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*dgJ8CQoCfQNh-q7VNRgPeQ.png" /><figcaption>Go to the “Actions ”tab to being building your Action.</figcaption></figure><p>In the page that loads, paste the following code into the editor.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/c62d756da500dcda53ddec2d52b9096a/href">https://medium.com/media/c62d756da500dcda53ddec2d52b9096a/href</a></iframe><p>The above .yml is tailored to my personal website, which was built with <a href="https://reactjs.org/">React</a> and <a href="https://www.gatsbyjs.org/">Gatsby</a>. Hence, you may not need all the steps listed.</p><ol><li>The Install dependencies step is only necessary if you used <a href="https://nodejs.org/en/">Node</a> packages.</li><li>The Build with Gatsby step can be replaced with your build command, if you have one.</li><li>The Azure upload step is the only required step. The first line in the script deletes all your old files in Azure, while the second uploads your local files. Since Gatsby generates my website files in the public folder, I upload that (-s parameter); you may need a different value there.</li></ol><p>After uploading your files, you may need to <a href="https://docs.microsoft.com/en-us/azure/cdn/cdn-purge-endpoint">purge your CDN endpoint</a> to see the updates. (Update: I now <a href="https://medium.com/@shilyndon/purging-azure-cdn-with-github-actions-1c18e2adaf18?source=friends_link&amp;sk=0e0936d055d9d5cfcb5f4847af0445c0">use GitHub actions to automate purging</a>.)</p><p>That’s it! Now, your content will automatically be uploaded to Azure Storage on every push.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=c81a63b32a9a" width="1" height="1" alt="">]]></content:encoded>
        </item>
    </channel>
</rss>