410 lines
28 KiB
HTML
410 lines
28 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" dir="auto">
|
||
|
||
<head><meta charset="utf-8">
|
||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||
<meta name="robots" content="index, follow">
|
||
<title>What I'm currently selfhosting. | Zachary Billman</title>
|
||
<meta name="keywords" content="">
|
||
<meta name="description" content="N.B.: I hope to add screenshots for each of these eventually. For now, I hope links to each services’ website will suffice.
|
||
|
||
|
||
Seafile
|
||
This is a great tool for managing files. I moved to this from Nextcloud because I am of the philosophy that I would prefer services that do one thing excellently instead of many things well. Nextcloud is an incredible Office365 replacement, but I found myself using a fraction of what it was capable of providing. Enter Seafile. It is cloud file syncing with a robust encryption implementation, just what the doctor ordered.">
|
||
<meta name="author" content="Zachary Billman">
|
||
<link rel="canonical" href="https://www.zacharybillman.com/posts/zpb-current-selfhosted/">
|
||
<link crossorigin="anonymous" href="/assets/css/stylesheet.2501c2c03e4bf83dbcd5f4c6f8fda43d8c7d579cf54417793281f3c19df525fb.css" integrity="sha256-JQHCwD5L+D281fTG+P2kPYx9V5z1RBd5MoHzwZ31Jfs=" rel="preload stylesheet" as="style">
|
||
<link rel="icon" href="https://www.zacharybillman.com/favicon.ico">
|
||
<link rel="icon" type="image/png" sizes="16x16" href="https://www.zacharybillman.com/favicon-16x16.png">
|
||
<link rel="icon" type="image/png" sizes="32x32" href="https://www.zacharybillman.com/favicon-32x32.png">
|
||
<link rel="apple-touch-icon" href="https://www.zacharybillman.com/apple-touch-icon.png">
|
||
<link rel="mask-icon" href="https://www.zacharybillman.com/safari-pinned-tab.svg">
|
||
<meta name="theme-color" content="#2e2e33">
|
||
<meta name="msapplication-TileColor" content="#2e2e33">
|
||
<link rel="alternate" hreflang="en" href="https://www.zacharybillman.com/posts/zpb-current-selfhosted/">
|
||
<noscript>
|
||
<style>
|
||
#theme-toggle,
|
||
.top-link {
|
||
display: none;
|
||
}
|
||
|
||
</style>
|
||
</noscript><script async defer data-website-id="cfe9001f-a59d-4e57-9df0-10551852558b" src="https://umami.zacharybillman.com/umami.js"></script>
|
||
<meta property="og:title" content="What I'm currently selfhosting." />
|
||
<meta property="og:description" content="N.B.: I hope to add screenshots for each of these eventually. For now, I hope links to each services’ website will suffice.
|
||
|
||
|
||
Seafile
|
||
This is a great tool for managing files. I moved to this from Nextcloud because I am of the philosophy that I would prefer services that do one thing excellently instead of many things well. Nextcloud is an incredible Office365 replacement, but I found myself using a fraction of what it was capable of providing. Enter Seafile. It is cloud file syncing with a robust encryption implementation, just what the doctor ordered." />
|
||
<meta property="og:type" content="article" />
|
||
<meta property="og:url" content="https://www.zacharybillman.com/posts/zpb-current-selfhosted/" /><meta property="article:section" content="posts" />
|
||
<meta property="article:published_time" content="2022-07-02T00:00:00+00:00" />
|
||
<meta property="article:modified_time" content="2022-07-02T00:00:00+00:00" /><meta property="og:site_name" content="Zachary Billman" />
|
||
|
||
<meta name="twitter:card" content="summary"/>
|
||
<meta name="twitter:title" content="What I'm currently selfhosting."/>
|
||
<meta name="twitter:description" content="N.B.: I hope to add screenshots for each of these eventually. For now, I hope links to each services’ website will suffice.
|
||
|
||
|
||
Seafile
|
||
This is a great tool for managing files. I moved to this from Nextcloud because I am of the philosophy that I would prefer services that do one thing excellently instead of many things well. Nextcloud is an incredible Office365 replacement, but I found myself using a fraction of what it was capable of providing. Enter Seafile. It is cloud file syncing with a robust encryption implementation, just what the doctor ordered."/>
|
||
|
||
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "BreadcrumbList",
|
||
"itemListElement": [
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 1 ,
|
||
"name": "Posts",
|
||
"item": "https://www.zacharybillman.com/posts/"
|
||
},
|
||
{
|
||
"@type": "ListItem",
|
||
"position": 2 ,
|
||
"name": "What I'm currently selfhosting.",
|
||
"item": "https://www.zacharybillman.com/posts/zpb-current-selfhosted/"
|
||
}
|
||
]
|
||
}
|
||
</script>
|
||
<script type="application/ld+json">
|
||
{
|
||
"@context": "https://schema.org",
|
||
"@type": "BlogPosting",
|
||
"headline": "What I'm currently selfhosting.",
|
||
"name": "What I\u0027m currently selfhosting.",
|
||
"description": "N.B.: I hope to add screenshots for each of these eventually. For now, I hope links to each services\u0026rsquo; website will suffice.\nSeafile\nThis is a great tool for managing files. I moved to this from Nextcloud because I am of the philosophy that I would prefer services that do one thing excellently instead of many things well. Nextcloud is an incredible Office365 replacement, but I found myself using a fraction of what it was capable of providing. Enter Seafile. It is cloud file syncing with a robust encryption implementation, just what the doctor ordered.\n",
|
||
"keywords": [
|
||
|
||
],
|
||
"articleBody": "N.B.: I hope to add screenshots for each of these eventually. For now, I hope links to each services’ website will suffice.\nSeafile\nThis is a great tool for managing files. I moved to this from Nextcloud because I am of the philosophy that I would prefer services that do one thing excellently instead of many things well. Nextcloud is an incredible Office365 replacement, but I found myself using a fraction of what it was capable of providing. Enter Seafile. It is cloud file syncing with a robust encryption implementation, just what the doctor ordered.\nPhotoprism\nNextcloud’s implementation of photo management was a bit clumsy for my tastes, so I began on a hunt for a more feature complete option. I was very lucky to stumble upon Photoprism early on in my self hosting journey. I am a proud supporter of this project. It brings a ton to the table, and doesn’t leave me missing Google Photos one bit. It is also actively developed. For instance, as of 2022-07-02, there was a recent update that improved scrolling a ton, which was one of my remaining quibbles with it’s functionality. Some people complain that it doesn’t scale up for libraries of 10,000+ photos, but my 2000 photo library works swimmingly. On my phone, I use the Folder Sync app to automatically send any pictures I take on my phone to the Photoprism imports folder. Within 15 minutes of snapping a photo, it is there on my Photoprism instance. I highly recommend giving it a go.\nMealie\nI have to admit that the name leaves something to be desired. Fortunately, the service itself doesn’t fall short. Mealie is a recipe management application. It is able to parse your favorite recipe websites to create immortalized versions of the recipe. This helps fight detestable link rot that plagues the internet. It allows you to edit ingredient amounts, make comments about what you would change next time, and plan out your weekly meals. It helps that it is beautiful and intuitive to use. If only I could get my partner to use it… I want to go on the record here and say that I think it is sensible that she doesn’t trust my self hosted services that much. In the early days of my self hosting journey, let’s just say that the up-time of my services were extremely finicky. I like to think they are more permanent now, especially because I am hosting everything on a VPS, but I certainly cannot compete with the stability of Google and Amazon. (Or pretty much any other paid service for that matter.) 😉\nGitea\nGitea is a community managed, lightweight code hosting solution written in Go. I use it to manage my data analysis (using privite repos) and even host the source code for this very website! Using Gitea has given me the surface level introduction to the behemoth that is git. Fortunately, that is all I need. Once I have removed any senitive data (like passwords, etc.) I will make my docker-compose file public. The docker-compose file is where I determine which services I pick to host, and how to configure them to my liking. Hopefully someone will find this useful.\nSyncthing\nIf I could only tell someone to self host a single service, I would have to pick Syncthing. It is a continuous file synchronization program, which means it keeps a specific file or folder the same between any device running Syncthing. I use it to keep my password database and my org-mode notes Now org-mode might be the biggest rabbithole of them all, and definitely the topic for another blog post. I use it to manage my TODO list, my lab notebook, my shopping lists, write this blog, and maintain my Zettelkasten note database. It is an immensely powerful tool. up to date between all of my devices. With Syncthing, all of my data is stored on my own devices and encrypted in transit.\nFreshRSS\nI keep up to date on the science in my field by subscribing to relevant RSS feeds. Yet another topic to write a post about. This really changed the game for my ability to keep on top of the latest research. 2022-07-14 Update: post complete! Check it out here. FreshRSS replaced Feedly for me, as I ran up against the number of feeds I could subscribe to on a free account. Also, when self hosting, I can save as many articles as my heart desires (even if I will never look at them again 😵 ).\nWallabag\nRemember Pocket for Firefox? (Yes, I know it still exists, okay?) Give Wallabag a spin, it is great for storing articles you want to read later. I set up FreshRSS to save articles to my Wallabag instance to keep for later.\nChangeDetection\nNow this is a fun one! ChangeDetection.io is a service that loads a webpage and checks to see if something has changed since the last time. This might not sound useful at first, but I recently used this to check for when a hot item for my new coffee roasting hobby was back in stock. I got an email the other day to let me know it was in stock. Pretty cool!\nHugo\nHugo is the static site generator I use to make this here blog! I was amazed at how much more response my site was using Hugo compare to when I paid a company to host a WordPress site. I’m sure WordPress could be fast, but I just wanted a barebones, pure html experience, and Hugo made it easy.\nCommento++\nYou may have noticed that I have some pretty snazzy comments sections on my site. I initially used Disqus, which you may recognize from a ton of sites around the internet, but didn’t like how it would serve ads through my comments section, and provided sign-ins using Google/Facebook/Twitter. I wanted to serve the simplest comments section I could, that still gave me the ability to migrate the comments to wherever I decide to keep my server. Go ahead and use the anonymous comment button, fill in your name, and you are off to the races.\nAuthelia\nThere are a few services I host that I really want to lock down. For these services, Authelia is great. It puts up a “Enter the 6 digit code on your phone” style 2 factor authentication in front of the services I choose. Or, if you follow the guide here, you can have it send you a DUO push, just like you are one of the big hitters out there. I feel much more secure with Authelia in front of Adminer, Traefik, and Portainer.\nBoring stuff that allows everything else to work!\nPostgreSQL database MariaDB database Traefik reverse proxy Umami privacy respecting site analytics Portainer for docker container management Redis in-memory data store Adminer database management traefik-socket-proxy to prevent pesky backdoors to my server! ",
|
||
"wordCount" : "1152",
|
||
"inLanguage": "en",
|
||
"datePublished": "2022-07-02T00:00:00Z",
|
||
"dateModified": "2022-07-02T00:00:00Z",
|
||
"author":[{
|
||
"@type": "Person",
|
||
"name": "Zachary Billman"
|
||
}],
|
||
"mainEntityOfPage": {
|
||
"@type": "WebPage",
|
||
"@id": "https://www.zacharybillman.com/posts/zpb-current-selfhosted/"
|
||
},
|
||
"publisher": {
|
||
"@type": "Organization",
|
||
"name": "Zachary Billman",
|
||
"logo": {
|
||
"@type": "ImageObject",
|
||
"url": "https://www.zacharybillman.com/favicon.ico"
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
</head>
|
||
|
||
<body class="" id="top">
|
||
<script>
|
||
if (localStorage.getItem("pref-theme") === "dark") {
|
||
document.body.classList.add('dark');
|
||
}
|
||
|
||
</script>
|
||
|
||
<header class="header">
|
||
<nav class="nav">
|
||
<div class="logo">
|
||
<a href="https://www.zacharybillman.com/" accesskey="h" title="Zachary Billman (Alt + H)">
|
||
<img src="https://www.zacharybillman.com/homepage/flask.svg" alt="" aria-label="logo"
|
||
height="35">Zachary Billman</a>
|
||
<div class="logo-switches">
|
||
<button id="theme-toggle" accesskey="t" title="(Alt + T)">
|
||
<svg id="moon" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||
</svg>
|
||
<svg id="sun" xmlns="http://www.w3.org/2000/svg" width="24" height="18" viewBox="0 0 24 24"
|
||
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||
stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="5"></circle>
|
||
<line x1="12" y1="1" x2="12" y2="3"></line>
|
||
<line x1="12" y1="21" x2="12" y2="23"></line>
|
||
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
|
||
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
|
||
<line x1="1" y1="12" x2="3" y2="12"></line>
|
||
<line x1="21" y1="12" x2="23" y2="12"></line>
|
||
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
|
||
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<ul id="menu">
|
||
<li>
|
||
<a href="https://www.zacharybillman.com/categories/" title="categories">
|
||
<span>categories</span>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a href="https://www.zacharybillman.com/tags/" title="tags">
|
||
<span>tags</span>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a href="https://www.zacharybillman.com/posts/" title="posts">
|
||
<span>posts</span>
|
||
</a>
|
||
</li>
|
||
<li>
|
||
<a href="https://www.zacharybillman.com/search/" title="search (Alt + /)" accesskey=/>
|
||
<span>search</span>
|
||
</a>
|
||
</li>
|
||
</ul>
|
||
</nav>
|
||
</header>
|
||
<main class="main">
|
||
|
||
<article class="post-single">
|
||
<header class="post-header">
|
||
<div class="breadcrumbs"><a href="https://www.zacharybillman.com/">Home</a> » <a href="https://www.zacharybillman.com/posts/">Posts</a></div>
|
||
<h1 class="post-title entry-hint-parent">
|
||
What I'm currently selfhosting.
|
||
</h1>
|
||
<div class="post-meta"><span title='2022-07-02 00:00:00 +0000 UTC'>Saturday, July 2, 2022</span> · 6 min · Zachary Billman
|
||
|
||
</div>
|
||
</header>
|
||
<div class="post-content"><p><em>N.B.: I hope to add screenshots for each of these eventually. For now, I hope links to each services’ website will suffice.</em></p>
|
||
<ol>
|
||
<li>
|
||
<p><a href="https://www.seafile.com/en/home/">Seafile</a></p>
|
||
<p>This is a great tool for managing files. I moved to this from Nextcloud because I am of the philosophy that I would prefer services that do one thing excellently instead of many things well. Nextcloud is an incredible Office365 replacement, but I found myself using a fraction of what it was capable of providing. Enter Seafile. It is cloud file syncing with a robust encryption implementation, just what the doctor ordered.</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://photoprism.app/">Photoprism</a></p>
|
||
<p>Nextcloud’s implementation of photo management was a bit clumsy for my tastes, so I began on a hunt for a more feature complete option. I was very lucky to stumble upon Photoprism early on in my self hosting journey. I am a proud supporter of this project. It brings a ton to the table, and doesn’t leave me missing Google Photos one bit. It is also actively developed. For instance, as of 2022-07-02, there was a recent update that improved scrolling a ton, which was one of my remaining quibbles with it’s functionality. Some people complain that it doesn’t scale up for libraries of 10,000+ photos, but my 2000 photo library works swimmingly. On my phone, I use the Folder Sync app to automatically send any pictures I take on my phone to the Photoprism imports folder. Within 15 minutes of snapping a photo, it is there on my Photoprism instance. I highly recommend giving it a go.</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://mealie.io/">Mealie</a></p>
|
||
<p>I have to admit that the name leaves something to be desired. Fortunately, the service itself doesn’t fall short. Mealie is a recipe management application. It is able to parse your favorite recipe websites to create immortalized versions of the recipe. This helps fight detestable <a href="https://en.wikipedia.org/wiki/Link_rot">link rot</a> that plagues the internet. It allows you to edit ingredient amounts, make comments about what you would change next time, and plan out your weekly meals. It helps that it is beautiful and intuitive to use. If only I could get my partner to use it…
|
||
<span class="sidenote-number"><small class="sidenote">
|
||
I want to go on the record here and say that I think it is sensible that she doesn’t trust my self hosted services that much. In the early days of my self hosting journey, let’s just say that the up-time of my services were extremely finicky. I like to think they are more permanent now, especially because I am hosting everything on a VPS, but I certainly cannot compete with the stability of Google and Amazon. (Or pretty much any other paid service for that matter.)
|
||
</small></span>
|
||
😉</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://gitea.io/en-us/">Gitea</a></p>
|
||
<p>Gitea is a community managed, lightweight code hosting solution written in Go. I use it to manage my data analysis (using privite repos) and even host <a href="https://gitea.zacharybillman.com/zpb/zacharybillman-hugo">the source code for this very website!</a> Using Gitea has given me the surface level introduction to the behemoth that is git. Fortunately, that is all I need. Once I have removed any senitive data (like passwords, etc.) I will make my docker-compose file public.
|
||
<span class="sidenote-number"><small class="sidenote">
|
||
The docker-compose file is where I determine which services I pick to host, and how to configure them to my liking.
|
||
</small></span>
|
||
Hopefully someone will find this useful.</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://syncthing.net/">Syncthing</a></p>
|
||
<p>If I could only tell someone to self host a single service, I would have to pick Syncthing. It is a continuous file synchronization program, which means it keeps a specific file or folder the same between any device running Syncthing. I use it to keep my <a href="https://keepassxc.org/">password database</a> and my <a href="https://orgmode.org/">org-mode</a> notes
|
||
<span class="sidenote-number"><small class="sidenote">
|
||
Now org-mode might be the biggest rabbithole of them all, and definitely the topic for another blog post. I use it to manage my TODO list, my lab notebook, my shopping lists, write this blog, and maintain my <a href="https://zettelkasten.de/introduction/">Zettelkasten note database</a>. It is an immensely powerful tool.
|
||
</small></span>
|
||
up to date between all of my devices. With Syncthing, all of my data is stored on my own devices and encrypted in transit.</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://freshrss.org/">FreshRSS</a></p>
|
||
<p>I keep up to date on the science in my field by subscribing to relevant RSS feeds.
|
||
<span class="sidenote-number"><small class="sidenote">
|
||
Yet another topic to write a post about. This really changed the game for my ability to keep on top of the latest research. <em>2022-07-14 Update: post complete! <a href="https://www.zacharybillman.com/posts/rss-feeds-to-find-science-papers/">Check it out here</a>.</em>
|
||
</small></span>
|
||
FreshRSS replaced Feedly for me, as I ran up against the number of feeds I could subscribe to on a free account. Also, when self hosting, I can save as many articles as my heart desires (even if I will never look at them again 😵 ).</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://wallabag.org/en">Wallabag</a></p>
|
||
<p>Remember Pocket for Firefox? (Yes, I know it still exists, okay?) Give Wallabag a spin, it is great for storing articles you want to read later. I set up FreshRSS to save articles to my Wallabag instance to keep for later.</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/dgtlmoon/changedetection.io">ChangeDetection</a></p>
|
||
<p>Now this is a fun one! ChangeDetection.io is a service that loads a webpage and checks to see if something has changed since the last time. This might not sound useful at first, but I recently used this to check for when a hot item for my new coffee roasting hobby was back in stock. I got an email the other day to let me know it was in stock. Pretty cool!</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://gohugo.io/">Hugo</a></p>
|
||
<p>Hugo is the static site generator I use to make this here blog! I was amazed at how much more response my site was using Hugo compare to when I paid a company to host a WordPress site. I’m sure WordPress could be fast, but I just wanted a barebones, pure html experience, and Hugo made it easy.</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://github.com/souramoo/commentoplusplus">Commento++</a></p>
|
||
<p>You may have noticed that I have some pretty snazzy comments sections on my site. I initially used Disqus, which you may recognize from a ton of sites around the internet, but didn’t like how it would serve ads through my comments section, and provided sign-ins using Google/Facebook/Twitter. I wanted to serve the simplest comments section I could, that still gave me the ability to migrate the comments to wherever I decide to keep my server. Go ahead and use the anonymous comment button, fill in your name, and you are off to the races.</p>
|
||
</li>
|
||
<li>
|
||
<p><a href="https://www.authelia.com/">Authelia</a></p>
|
||
<p>There are a few services I host that I really want to lock down. For these services, Authelia is great. It puts up a “Enter the 6 digit code on your phone” style 2 factor authentication in front of the services I choose. Or, <a href="https://www.smarthomebeginner.com/docker-authelia-tutorial/">if you follow the guide here</a>, you can have it send you a DUO push, just like you are one of the big hitters out there. I feel much more secure with Authelia in front of Adminer, Traefik, and Portainer.</p>
|
||
</li>
|
||
<li>
|
||
<p>Boring stuff that allows everything else to work!</p>
|
||
<ul>
|
||
<li>PostgreSQL database</li>
|
||
<li>MariaDB database</li>
|
||
<li>Traefik reverse proxy</li>
|
||
<li>Umami privacy respecting site analytics</li>
|
||
<li>Portainer for docker container management</li>
|
||
<li>Redis in-memory data store</li>
|
||
<li>Adminer database management</li>
|
||
<li>traefik-socket-proxy to prevent pesky backdoors to my server!</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
|
||
|
||
</div>
|
||
|
||
<footer class="post-footer">
|
||
<ul class="post-tags">
|
||
</ul>
|
||
<nav class="paginav">
|
||
<a class="prev" href="https://www.zacharybillman.com/posts/my-selfhosting-journey/">
|
||
<span class="title">« Prev</span>
|
||
<br>
|
||
<span>My selfhosting journey.</span>
|
||
</a>
|
||
<a class="next" href="https://www.zacharybillman.com/posts/the-uncommon-yellowthroat/">
|
||
<span class="title">Next »</span>
|
||
<br>
|
||
<span>The (un)common yellowthroat.</span>
|
||
</a>
|
||
</nav>
|
||
|
||
</footer><script defer src="https://commento.zacharybillman.com/js/commento.js"></script>
|
||
<noscript>
|
||
Please enable JavaScript to view the
|
||
<a href="https://commento.io" rel="nofollow">
|
||
comments powered by Commento++.
|
||
</a>
|
||
</noscript>
|
||
<div id="commento"></div>
|
||
|
||
</article>
|
||
</main>
|
||
|
||
<footer class="footer">
|
||
<span>© 2024 <a href="https://www.zacharybillman.com/">Zachary Billman</a></span> ·
|
||
|
||
<span>
|
||
Powered by
|
||
<a href="https://gohugo.io/" rel="noopener noreferrer" target="_blank">Hugo</a> &
|
||
<a href="https://github.com/adityatelange/hugo-PaperMod/" rel="noopener" target="_blank">PaperMod</a>
|
||
</span>
|
||
</footer>
|
||
<a href="#top" aria-label="go to top" title="Go to Top (Alt + G)" class="top-link" id="top-link" accesskey="g">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 6" fill="currentColor">
|
||
<path d="M12 6H0l6-6z" />
|
||
</svg>
|
||
</a>
|
||
|
||
<script>
|
||
let menu = document.getElementById('menu')
|
||
if (menu) {
|
||
menu.scrollLeft = localStorage.getItem("menu-scroll-position");
|
||
menu.onscroll = function () {
|
||
localStorage.setItem("menu-scroll-position", menu.scrollLeft);
|
||
}
|
||
}
|
||
|
||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||
anchor.addEventListener("click", function (e) {
|
||
e.preventDefault();
|
||
var id = this.getAttribute("href").substr(1);
|
||
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView({
|
||
behavior: "smooth"
|
||
});
|
||
} else {
|
||
document.querySelector(`[id='${decodeURIComponent(id)}']`).scrollIntoView();
|
||
}
|
||
if (id === "top") {
|
||
history.replaceState(null, null, " ");
|
||
} else {
|
||
history.pushState(null, null, `#${id}`);
|
||
}
|
||
});
|
||
});
|
||
|
||
</script>
|
||
<script>
|
||
var mybutton = document.getElementById("top-link");
|
||
window.onscroll = function () {
|
||
if (document.body.scrollTop > 800 || document.documentElement.scrollTop > 800) {
|
||
mybutton.style.visibility = "visible";
|
||
mybutton.style.opacity = "1";
|
||
} else {
|
||
mybutton.style.visibility = "hidden";
|
||
mybutton.style.opacity = "0";
|
||
}
|
||
};
|
||
|
||
</script>
|
||
<script>
|
||
document.getElementById("theme-toggle").addEventListener("click", () => {
|
||
if (document.body.className.includes("dark")) {
|
||
document.body.classList.remove('dark');
|
||
localStorage.setItem("pref-theme", 'light');
|
||
} else {
|
||
document.body.classList.add('dark');
|
||
localStorage.setItem("pref-theme", 'dark');
|
||
}
|
||
})
|
||
|
||
</script>
|
||
<script>
|
||
document.querySelectorAll('pre > code').forEach((codeblock) => {
|
||
const container = codeblock.parentNode.parentNode;
|
||
|
||
const copybutton = document.createElement('button');
|
||
copybutton.classList.add('copy-code');
|
||
copybutton.innerHTML = 'copy';
|
||
|
||
function copyingDone() {
|
||
copybutton.innerHTML = 'copied!';
|
||
setTimeout(() => {
|
||
copybutton.innerHTML = 'copy';
|
||
}, 2000);
|
||
}
|
||
|
||
copybutton.addEventListener('click', (cb) => {
|
||
if ('clipboard' in navigator) {
|
||
navigator.clipboard.writeText(codeblock.textContent);
|
||
copyingDone();
|
||
return;
|
||
}
|
||
|
||
const range = document.createRange();
|
||
range.selectNodeContents(codeblock);
|
||
const selection = window.getSelection();
|
||
selection.removeAllRanges();
|
||
selection.addRange(range);
|
||
try {
|
||
document.execCommand('copy');
|
||
copyingDone();
|
||
} catch (e) { };
|
||
selection.removeRange(range);
|
||
});
|
||
|
||
if (container.classList.contains("highlight")) {
|
||
container.appendChild(copybutton);
|
||
} else if (container.parentNode.firstChild == container) {
|
||
|
||
} else if (codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.nodeName == "TABLE") {
|
||
|
||
codeblock.parentNode.parentNode.parentNode.parentNode.parentNode.appendChild(copybutton);
|
||
} else {
|
||
|
||
codeblock.parentNode.appendChild(copybutton);
|
||
}
|
||
});
|
||
</script>
|
||
</body>
|
||
|
||
</html>
|