blog stuff

This commit is contained in:
Evert Prants 2022-10-16 12:37:13 +03:00
parent b04fb7f381
commit 2a48d4b6da
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
26 changed files with 1230 additions and 11 deletions

View File

@ -0,0 +1,225 @@
.blog {
width: 100%;
min-height: 100vh;
background-color: rgb(241, 241, 241);
a {
color: #258fb8;
}
&__header {
text-align: center;
padding: 2rem;
a {
color: #000;
}
}
&__content {
display: flex;
gap: 1.5rem;
max-width: 1280px;
margin: 0 auto;
@media all and (max-width: 768px) {
flex-direction: column;
gap: 0;
}
}
&__main-col {
@media all and (min-width: 768px) {
width: 70%;
}
@media all and (min-width: 1080px) {
width: 90%;
}
img {
max-width: 100%;
height: auto;
display: block;
margin: auto;
}
}
&-post {
margin: 50px 0;
&__title {
padding: 20px 20px 0 20px;
h1 {
font-size: 2rem;
}
a {
color: #000;
}
}
&__content {
color: #555;
padding: 0 20px;
code {
background: #eee;
text-shadow: 0 1px #fff;
padding: 0 0.3em;
}
.codeblock {
background: #2d2d2d;
padding: 15px 20px;
margin: 0 -20px;
border-style: solid;
border-color: #ddd;
border-width: 1px 0;
overflow: auto;
color: #ccc;
line-height: 22px;
width: calc(100% + 40px);
pre {
margin: 0;
}
code {
background: transparent;
text-shadow: none;
padding: 0;
}
}
h1,
h2,
h3,
h4,
h5 {
line-height: 1.1em;
margin: 1.1em 0;
font-weight: bold;
}
p,
table {
margin: 1.6em 0;
}
h1 {
font-size: 2em;
}
h2 {
font-size: 1.5em;
}
h3 {
font-size: 1.3em;
}
h4 {
font-size: 1.2em;
}
h5 {
font-size: 1.1em;
}
ul,
ol,
dl {
margin: 0 20px;
line-height: 1.6em;
padding: 0;
margin-top: 1.6em;
margin-bottom: 1.6em;
}
}
&__inner {
overflow: hidden;
background-color: #fff;
}
&__meta {
margin-bottom: 1em;
margin-left: 5px;
font-size: 14px;
a {
letter-spacing: 2px;
color: #999;
line-height: 1em;
text-shadow: 0 1px #fff;
font-weight: bold;
}
}
&__footer {
font-size: 0.85em;
line-height: 1.6em;
border-top: 1px solid #ddd;
padding-top: 1.6em;
margin: 0 20px 20px;
}
&__tags {
display: flex;
gap: 1rem;
.blog-post__tag {
color: #999;
}
}
}
&__sidebar {
&-block {
margin: 50px 0;
}
&-title {
font-size: 0.85em;
text-decoration: none;
text-transform: uppercase;
letter-spacing: 2px;
color: #999;
margin-bottom: 1em;
margin-left: 5px;
line-height: 1em;
text-shadow: 0 1px #fff;
font-weight: bold;
}
&-content {
color: #777;
text-shadow: 0 1px #fff;
background: #ddd;
box-shadow: 0 -1px 4px #ccc inset;
border: 1px solid #ccc;
padding: 15px;
border-radius: 3px;
line-height: 1.6em;
word-wrap: break-word;
font-size: 0.9em;
}
a {
color: #258fb8;
}
ul {
list-style-type: none;
padding: 0;
margin: 0;
}
.tag-cloud a {
margin-right: 5px;
display: inline-block;
}
}
}

View File

@ -1,4 +1,4 @@
section {
.section {
display: flex;
flex-direction: column;
color: #eee;

View File

@ -2,3 +2,4 @@
@use 'section';
@use 'icon-link-list';
@use 'featured-project';
@use 'blog';

View File

@ -1,4 +1,5 @@
@use '@/assets/iconfont/css/lunasquee-site.css';
@use 'highlight.js/scss/dark.scss';
@use 'components/index';
@use 'helpers';

38
components/blog-post.vue Normal file
View File

@ -0,0 +1,38 @@
<template>
<article class="blog-post">
<div class="blog-post__meta">
<a :href="'/blog/' + post.fullSlug">
<time :datetime="new Date(post.date).toISOString()">
{{ post.date }}
</time>
</a>
</div>
<div class="blog-post__inner">
<header class="blog-post__title">
<h1>
<template v-if="detail">
{{ post.title }}
</template>
<template v-else>
<a :href="'/blog/' + post.fullSlug">{{ post.title }}</a>
</template>
</h1>
</header>
<div class="blog-post__content" v-html="post.html"></div>
<div class="blog-post__footer">
<div class="blog-post__tags">
<a
v-for="tag of post.tags"
:href="'/blog/tags/' + tag"
class="blog-post__tag"
>#{{ tag }}</a
>
</div>
</div>
</div>
</article>
</template>
<script setup lang="ts">
defineProps<{ post: any; detail?: boolean }>();
</script>

View File

@ -0,0 +1,12 @@
<template>
<div class="blog__sidebar-block">
<h2 class="blog__sidebar-title">{{ title }}</h2>
<div class="blog__sidebar-content">
<slot></slot>
</div>
</div>
</template>
<script setup lang="ts">
defineProps<{ title: string }>();
</script>

View File

@ -1,5 +1,5 @@
<template>
<section>
<section class="section">
<div class="section-header">
<slot name="header"></slot>
</div>

View File

@ -0,0 +1,130 @@
---
title: Giving a shot at making a game using Godot Engine 3.0
tags:
- game
- projects
- godot
date: 2018-02-03 12:43:10
---
Hello!
In the past week, Ive been working on a simple 3D game using [Godot Engine 3.0](https://godotengine.org/) (_go-doh_). In this article, I will show off some of the features and discuss how it all works. Heres a quick demo of the gameplay:
# Mechanics
## Block breaking
Blocks can be broken by just clicking on a tile of the chunk. The speed of the breaking and the damage dealt to the tile depend on the current pickaxe. Pickaxes can be upgraded in the Shop GUI. Pickaxes also have a durability value, which will always be kept at maximum when youre on the surface. The current pickaxe along with its durability is displayed in the bottom right corner of the screen. When your pickaxe “breaks”, you will stop mining and you wont be able to start mining until you repair it or return to the surface.
## Bombing
Blocks can also be broken using bombs. By placing a bomb, it takes approximately 3 seconds to detonate and it **damages** blocks in a 4 tile radius. Tougher blocks located deeper in the mine wont be broken by a bomb, instead made softer and thus they will take less time to mine with a pickaxe. When a bomb blows up tiles, you wont get them in your inventory.
## Shop
In the shop GUI, you can sell your mined blocks for money or buy items like torches, bombs and better pickaxes. Money can only be earned by selling the blocks youve mined.
## Minerals
This is the list of minerals currently generated in the chunk and their yield when sold:
- Copper - $15
- Iron - $20
- Silver - $50
- Gold - $100
- Amethyst - $250
- Emerald - $300
- Sapphire - $400
- Ruby - $500
- Diamond - $1000
- Flawless Diamond - $2500 (rarest)
Note that all of these are not guaranteed to generate. It depends on the random seed and the y value.
# Scenes
Godot uses Scenes, which can be easily instanced. The following image is the Godot editor, in it I have opened the root scene, which will be the point where the game runs.
![Root Scene](https://i.lunasqu.ee/blog/root_scene.png)
On the right, you can see the scene tree.
![Instances](https://i.lunasqu.ee/blog/instances.png)
- [**Root**](http://docs.godotengine.org/en/3.0/classes/class_spatial.html) - This is the first node in the scene, containing everything.
- [**WorldEnvironment**](http://docs.godotengine.org/en/3.0/classes/class_worldenvironment.html) - Controls the overall look of the scene. I have configured it to give me a completely black scene without a background.
- All of my other custom scenes follow these.
## The Chunk
The Chunk is a [MeshInstance](http://docs.godotengine.org/en/3.0/classes/class_meshinstance.html) which I use to generate a random mine with. Ive attached a script to this node which generates a mesh and assigns it to the MeshInstance.
Using [SurfaceTool](http://docs.godotengine.org/en/3.0/classes/class_surfacetool.html), you can create geometry by specifying the vertices individually.
```
# Create a new SurfaceTool instance
var st = SurfaceTool.new()
# Begin specifying your vertices using primitive type of triangles. Every 3 vertices is a triangle.
st.begin(Mesh.PRIMITIVE_TRIANGLES)
# Half of a quad
st.add_uv(Vector2(1,1))
st.add_vertex(Vector3(0,0,1))
st.add_uv(Vector2(1,0))
st.add_vertex(Vector3(0,1,1))
st.add_uv(Vector2(0,1))
st.add_vertex(Vector3(1,0,1))
# Output the mesh
var mesh = st.commit()
```
This is just a basic example of what you can do with SurfaceTool, but as you can see, its a great way to create procedural geometry. Alongside this I use some code to decide which material to use for which tile and the ability to regenerate the mesh when a tile is changed.
## The Player
The player is currently just a simple CapsuleMesh with a camera, a light, some raycasts and some sounds attached to it.
![Player Scene](https://i.lunasqu.ee/blog/player_scene.png)
The script attached to it deals with [KinematicBody](http://docs.godotengine.org/en/3.0/classes/class_kinematicbody.html) movement and the block break animation. The RayCasts are used to determine whether the player is colliding with the chunk or not. The RayCast pointing up is used to cancel the jump when player hits a tile from below.
## The GUI
![GUI Scene](https://i.lunasqu.ee/blog/gui.png)
GUI in Godot is created using the [Control](http://docs.godotengine.org/en/3.0/classes/class_control.html) class and its inherited classes.
In the GUI scene I have a simple [ItemList](http://docs.godotengine.org/en/3.0/classes/class_itemlist.html) inventory and a few other items. The main thing the GUI does is the shop: buy and sell system.
![The Shop Dialog](https://i.lunasqu.ee/blog/shop.png)
On the righthand side, there are two image buttons that spawn an instance of another scene and translate it to the players current position. These two buttons are Bomb and Torch, which “place” their respective item.
```
# Load the scene
var scene = load("res://scene/props/bomb.tscn")
# Get the root instance of the scene, in this case it's a Spatial
var node = scene.instance()
# Translate the Spatial
node.set_translation(Vector3(x, y, z))
# Add it to the Root scene
get_node("../").add_child(node)
```
## SideSupport
This scene generates 4 quads that cover the chunk. These prevent the player from walking off of the chunk or falling out the bottom.
## CrackBox
This scene generates a quad that has a variable texture. This scene is used to display the breaking animation of a tile, by translating it to the currently “broken” tile and setting the Texture of the material to one of the 6 crack textures.
![The 6 textures used to represent the tile breaking](https://i.lunasqu.ee/blog/crack.png)
# The Source
The source of this project is available [on my GitHub account](https://github.com/LunaSquee/xyminer). Ive laid out the scenes, scripts, etc. into a nice folder structure so you can easily see where the things are located. I recommend you do the same when youre making a game in Godot. If you want to try the game yourself, you can clone or download the repository onto your computer and **Import** it in the project list of the editor. Downloads are not currently available as the game is still in development, but you can easily export them in the editor.
# Conclusion
I think that Godot is a really great game engine. It lets you do amazing things quite easily. The community is also very helpful and the [documentation](http://docs.godotengine.org/en/3.0/) is good enough for you to get started. This game still needs work for it to be enjoyable, but its been quite fun getting this far and I thought Id share it with you.

View File

@ -0,0 +1,218 @@
---
title: How to install Arch Linux
tags:
- linux
- tutorial
date: 2018-03-15 17:21:22
---
Today Im going to instruct you through the steps of installing your own Arch Linux system.
## Download the ISO
You can find the [downloads for Arch Linux by clicking here](https://www.archlinux.org/download/).
## 1. Partition the drive
Determine your drive name by running `lsblk`. Run the following command to start the partitioning (with `/dev/sda` replaced by your drive):
```
# fdisk /dev/sda
```
1. Enter `n` to create a new partition.
2. Press Enter on the following prompts to accept the defaults.
3. Enter `w` to write your changes to the disk.
## 2. Create the File System and mount it
Next, you need to create a filesystem on the partition that youve made. After youve created the filesystem, you can mount it.
```
# mkfs.ext4 /dev/sda1
# mount -t ext4 /dev/sda1 /mnt
```
## 3. Install Arch Linux system
This is maybe the most important step in the progress. This is the step where you are actually installing the system. For that, you need to run `pacstrap` followed by the packages you want to install. In this case, well install `base` and `grub` for the bootloader.
```
# pacstrap -i /mnt base grub
```
## 4. Generate File System Tab
The file `fstab` contains descriptive information about the filesystems the system can mount.
```
# genfstab -U -p /mnt >> /mnt/etc/fstab
```
## 5. Enter the system via `chroot`
Now, were ready to set up the system by going inside it. For this, well be running `arch-chroot`. **Do NOT reboot at this stage!**
```
# arch-chroot /mnt /bin/bash
```
## 6. Setting up locales
Locales are basically the language files your system will be using.
```
# nano /etc/locale.gen
Uncomment line en_US.UTF-8 UTF-8
Uncomment line en_US ISO-8859-1
(uncomment by removing the `#` in front)
```
After that, you have to generate the locale files.
```
# locale-gen
```
And then put them into effect on the system.
```
# echo LANG=en_US.UTF-8 > /etc/locale.conf
# export LANG=en_US.UTF-8
```
## 7. Setting the timezone
All timezone files can be found in `/usr/share/zoneinfo/`. You need to create a link to `/etc/localtime` with the timezone you want.
```
# ln -s /usr/share/zoneinfo/LOCATION/CITY /etc/localtime
```
## 8. Set the hardware clock
This command sets the hardware clock from system time. `--utc` implies that your hardware clock is in the UTC timezone. More information [here](https://wiki.archlinux.org/index.php/Time#Hardware_clock)
```
# hwclock --systohc --utc
```
## 9. Set your systems hostname
This can be anything you want.
```
# echo "my-computer" > /etc/hostname
```
## 10. Set up simple networking
Before you do the following, please run `ip a` to determine your interface. Its generally called `eth0`, but it may be different on some machines.
1. Copy the example
```
# cp /etc/netctl/examples/ethernet-dhcp /etc/netctl/my_network
```
2. Modify the interface name
```
# nano /etc/netctl/my_network
Replace the "Interface=eth0" line with the one shown by the "ip a" command.
```
3. Enable the network
```
# netctl enable my_network
```
##
Setting a password on your root user is highly recommended. You can do that now by running `passwd`
##
You can find information on how to install GRUB on various systems on the [Arch Linux Wiki](https://wiki.archlinux.org/index.php/GRUB). The following is a simple installation on a system with a **BIOS** (not **UEFI**). You can find more boot loaders [here](https://wiki.archlinux.org/index.php/Category:Boot_loaders).
```
# grub-install --recheck /dev/sda
# grub-mkconfig -o /boot/grub/grub.cfg
```
## 13. Reboot the machine
You are now ready to boot into your new system.
```
# exit
# umount /mnt
# shutdown
```
After shutting down, remove the disk image and boot the system again. **You can now use your newly installed system!**
# More things to do
## Creating an user account
The first user you create should also be added to the `wheel` group. This will be kind of like an administrator.
```
# useradd -m -G wheel -s /bin/bash usernamehere
# passwd usernamehere
```
For creating other users, you should omit the `-G wheel` part unless you want them to be able to use `sudo`.
### Enabling `sudo`
1. Install sudo - `# pacman -S sudo`
2. Create a file in `/etc/sudoers.d/99-wheel` with the following lines:
```
%wheel ALL=(ALL) ALL
```
If you want the `wheel` group to be able to use sudo without password, you can replace the last `ALL` with `NOPASSWD: ALL`.
## Installing a Desktop Environment
Firstly, you should install `xorg` packages.
```
# pacman -S xorg-server xorg-xinit xorg-server-utils mesa xterm
```
For graphics drivers, you can look at the wiki: [Intel](https://wiki.archlinux.org/index.php/Intel_graphics), [ATI](https://wiki.archlinux.org/index.php/ATI), [AMD](https://wiki.archlinux.org/index.php/AMDGPU) and [NVIDIA](https://wiki.archlinux.org/index.php/NVIDIA).
**You can find packages for various desktop environments [here](https://wiki.archlinux.org/index.php/Desktop_environment).**
### Install `lightdm`
Some desktop environments dont have a system to log you in to a session. This is where `lightdm` comes to play.
```
# pacman -S lightdm lightdm-gtk-greeter
# systemctl enable lightdm.service
```
You can find lightdm configuration at `/etc/lightdm/lightdm.conf`.
After youve installed what you want, you can reboot the system and it should either load up the desktop environment or the `lightdm-gtk-greeter`, which will allow you to select a desktop environment to log in to.
## Keeping your system up-to-date
You should keep your system up-to-date to get the latest packages and security patches. Arch Linux is a **rolling release** distribution, meaning that you dont have to reinstall the entire system and package updates are rolled separately instead of in bundles.
```
# pacman -Syu
```
This command synchronizes your system with the latest repositories and installs all available updates.
# Thats it!
This should be your basic Arch Linux system ready to go! Always remember that the [Arch Linux Wiki](https://wiki.archlinux.org/) is a great place to find help for all things Linux, not just Arch!

View File

@ -0,0 +1,114 @@
---
title: Self-hosting, Part 1
tags:
- linux
- tutorial
date: 2019-02-23 23:17:18
---
Today I will be describing to you my experiences with self-hosting and how you can get started as well.
Im not going to go into detail in this article about how to install and configure anything, but the websites for respective pieces of software have great documentation and you can always look for more information online. Followup blog posts may come in the future describing setups that Ive created.
## 1. Hardware
Believe it or not, you dont actually need a server computer to host your websites, email or other networked software. You can use pretty much any old computer to do what you want. I know a guy who uses a laptop without a screen as a server, and quite successfully so. Your server could be anything, from an old Android phone to a router to an actual server computer, just as long as you have it connected to a network.
I have personally been using [Raspberry Pi](https://www.raspberrypi.org/) computers for my personal servers. What you might learn the hard way, though, that you really dont want to do many write operations on a simple SD card, instead, I recommend getting a 2.5” Hard Disk Drive or a even a Solid State Drive with a simple USB to SATA converter, which you can get cheap off [eBay](https://www.ebay.com/sch/i.html?_from=R40&_trksid=m570.l1313&_nkw=usb+to+sata&_sacat=0) or [AliExpress](https://www.aliexpress.com/wholesale?SearchText=usb+to+sata). Ive had quite a few corrupted SD cards on my Raspberry Pis due to high amount of concurrent writes.
### 1.1. Hosting services
For most of the heavy-lifting, though, I recommend using Virtual Private Servers for online services. I use a 10€/month cloud server from [Hetzner](https://www.hetzner.com/cloud) to host most of my websites, databases and email. I can only really recommend Hetzner and [OVH](https://www.ovh.com/) because Ive had the best experiences with them, but you can search around the web for VPS hosting. Be sure to look at reviews and make sure to read the Terms of Service notices!
If you have more money to spend, you can also look for hosted dedicated servers.
With hosting services, you also avoid the problem of having to expose your IP address or open ports on your local network. If you dont like your IP address being public or you simply are unable to open ports on your network, I strongly recommend that you get a low cost virtual private server instead of any local computer.
## 2. Operating System
What youre looking for here is an operating system based on the [Linux kernel](https://en.wikipedia.org/wiki/Linux_kernel). I can recommend [Debian](https://www.debian.org/intro/about), [CentOS](https://www.centos.org/) and [Arch Linux](https://www.archlinux.org/). Linux systems are (mostly, there are paid enterprise solutions) completely free, in every sense of the word, and open source.
If you run into any issues with Linux systems, or just want to know how to configure or use a certain program, an absolutely wonderful online resource is the [Arch Linux Wiki](https://wiki.archlinux.org/), and you dont even have to use Arch Linux in order for it to be useful to you.
## 3. Software
Theres _a lot_ of things you can run on your own server computer, so in this section, Im going to tell you about some of the more common and more useful services.
### 3.1. Website hosting
Of course everybody wants to have a website. What would a self-hosted server be without a website?
The first thing you need in order to run a website is a web server. For beginners and simple setups, the most popular choice is the [Apache HTTP Server](https://httpd.apache.org/), however for more versatility and better (in my opinion) configuration and proxying, I recommend [nginx](http://nginx.org/) as well.
For the actual content of the website, you also have _a lot_ of options. A very good and popular free platform you can use is [WordPress](https://wordpress.org/). Of course, theres the possibility of writing a website yourself, either in PHP and HTML or just HTML and CSS.
### 3.2. Private File Cloud
If youre like me and dont like having your files on **someone elses computer**, hosting a “file cloud” yourself is a good bet. The best software to do this (by far) is [NextCloud](https://nextcloud.com/). It allows you to easily synchronize files between your computers and mobile phones and you always know that your files are safe in your own hands.
Be warned though, this application server takes a good chunk of your computer resources, so you might need something more powerful than a Raspberry Pi for this one.
#### 3.2.1. Online Documents
NextCloud can very easily help you transfer documents between your devices, but theres also the option to integrate an Office platform, such as [Collabora Online](https://nextcloud.com/collaboraonline/), which allows you to view and edit your Office documents on the go, just like Google Docs or Office 356 Online, but youre in control!
Be warned again, these applications are also a significant load on your server.
### 3.3. Mail Server
Okay, so youve got it in your head that you also want to host a mail server for yourself, but let me stop you right there. Setting up a mail server is **a really tedious and time consuming task** and youll probably end up giving up and using Gmail or Outlook in the end anyway. A misconfigured email server could also do a lot of harm, so please be careful with what you configure and always read the documentation if you really do plan on hosting your own mail server.
If that didnt scare you, you may proceed. Just know that you should **NEVER** host an open email relay unless you really know what youre doing.
#### 3.3.1. Mail Transfer Agent (MTA)
The MTA is the program used to send emails. It does not necessarily let you read/manage inboxes on its own.
One of the more popular MTAs, and the one I use, is [Postfix](http://www.postfix.org/). Creating the perfect configuration to meet your needs is, again, a tedious task. You can look for example configurations online and try them out yourself.
#### 3.3.2. Mail access (IMAP, POP3)
This is the application that actually allows you to access and store your emails on your server.
The most popular software for this is [Dovecot](https://www.dovecot.org/). Dovecot is really good at doing what it does and it allows you to set up mailboxes and can even do message sorting ([pigeonhole](https://wiki2.dovecot.org/Pigeonhole) sieve).
#### 3.3.3. Mail filtering
Mail filtering is what you would use to combat spam.
A great free software for combating spam is [SpamAssassin](https://spamassassin.apache.org/) and you can use [ClamAV](https://www.clamav.net/) to detect viruses in incoming mail.
### 3.4. Database
If you need a database, your own server is also a good place for that. For SQL databases, I recommend using [MariaDB](https://mariadb.org/). For NoSQL, you could use [MongoDB](https://www.mongodb.com/). Youll need a MariaDB database if youre going to host WordPress on your server.
## 4. Communication
Communication is an important part of society. Did you know that there are free and open source solutions that you can put on your own server that allows you to communicate with your friends? Here are some that I have personally come in contact with and can recommend.
**1. An aging, historical solution: IRC**
IRC (Internet Relay Chat) is an old text-based protocol used in ages old chat rooms. Believe it or not, it is still used to this day. IRC is a quick and easy way to communicate in text. A popular IRC server application is [inspIRCd](http://www.inspircd.org/).
I personally still host an IRC server for me and my friends.
**2. Purely for voice chatting: Mumble**
[Mumble](https://wiki.mumble.info/wiki/Main_Page) is a free and open source low latency voice chatting application. In order to host your own server for Mumble, you need [Murmur](https://wiki.mumble.info/wiki/Running_Murmur) (mumble-server).
**3. Up-and-coming communication standard: Matrix**
[Matrix](https://matrix.org/blog/home/) is a free and open source decentralized communication protocol. Anyone can host a server (called a homeserver) and people from different servers are able to communicate with each-other. The current go-to homeserver solution is [Synapse](https://matrix.org/docs/guides/installing-synapse), however more options are coming in the future.
As a disclaimer Id like to say that Synapse is a large application and it might not be plausible for you to host it on your machine.
**4. Simple and modern: Rocket.Chat**
[Rocket.Chat](https://rocket.chat/) is a free and open source communication platform aimed for teams. Despite being aimed for teams, its also a great solution for chatting with your friends. It allows you to create rooms and also provides end-to-end encryption for messages.
Ive personally hosted a Rocket.Chat instance and can say that it is a good platform, however me and my buddies are old-school and like our IRC server more.
### 4.1. Your own social network!
Have you ever thought about running a Twitter-like platform that you had full control over? Well, now you can! With [Mastodon](https://joinmastodon.org/), you can create your own social networking platform, called an instance. Instances are joined together in a global federated network called the [Fediverse](https://en.wikipedia.org/wiki/Fediverse), which allows you to communicate with anyone on any Mastodon (or other platform on the Fediverse) server without making multiple accounts.
## Conculsion, for now..
Theres a lot of things you can do with your own server. Hopefully this blog post was of any use to you.

View File

@ -0,0 +1,15 @@
---
title: Welcome to my blog!
tags:
- projects
- update
date: 2018-01-26 12:00:00
---
So, Ive decided to start a new blog using [Hexo](https://hexo.io/).
Ill probably be using this to share my projects and write some tutorials.
## Current projects
Im currently taking a break from writing code due to personal reasons, but Im mainly working on my network, [Icy Network](https://icynet.eu). The main project in the works is [Episodes.Community](https://github.com/IcyNet/Episodes.Community), which is basically a site where people can watch and share links to their favorite TV shows and cartoons.

104
layouts/blog.vue Normal file
View File

@ -0,0 +1,104 @@
<template>
<div class="blog">
<header class="blog__header">
<h1><a href="/blog">Blog</a></h1>
</header>
<section class="blog__content">
<div class="blog__main-col">
<slot></slot>
</div>
<div class="blog__sidebar">
<BlogSidebar title="Tags">
<ul>
<li v-for="tag of tags">
<a :href="'/tags/' + tag.name">{{ tag.name }}</a>
</li>
</ul>
</BlogSidebar>
<BlogSidebar title="Tag cloud">
<div class="tag-cloud">
<a
v-for="tag of tags"
:href="'/blog/tags/' + tag.name"
:style="{ fontSize: getFontSize(tag) }"
>{{ tag.name }}</a
>
</div>
</BlogSidebar>
<BlogSidebar title="Archive">
<ul>
<li v-for="archive of monthList">
<a :href="archive.href">{{ archive.name }}</a>
</li>
</ul>
</BlogSidebar>
</div>
</section>
</div>
</template>
<script setup lang="ts">
const { data: tags } = await useFetch('/api/blog/tags');
const { data: archive } = await useFetch('/api/blog/archive');
const monthNames = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December',
];
const monthList = computed(() => {
let links = [];
const res = archive.value;
for (const year of Object.keys(res).sort((a, b) => Number(b) - Number(a))) {
for (const month of Object.keys(res[year]).sort(
(a, b) => Number(b) - Number(a)
)) {
const monthName = monthNames[new Date(`${year}-${month}-01`).getMonth()];
links.push({
name: `${monthName} ${year}`,
href: `/blog/archive/${year}/${month}`,
});
}
}
return links;
});
const minTag = computed(() =>
tags.value.reduce<number>(
(min, current) => (min > current.count ? current.count : min),
100
)
);
const maxTag = computed(() =>
tags.value.reduce<number>(
(max, current) => (max < current.count ? current.count : max),
0
)
);
function convertRange(value: number, r1: number[], r2: number[]) {
return ((value - r1[0]) * (r2[1] - r2[0])) / (r1[1] - r1[0]) + r2[0];
}
const getFontSize = (tag: any): string => {
return convertRange(tag.count, [minTag.value, maxTag.value], [10, 20]) + 'px';
};
</script>

167
lib/blog/read-posts.ts Normal file
View File

@ -0,0 +1,167 @@
import { join } from 'path';
import fs from 'fs/promises';
import yaml from 'yaml';
import { marked } from 'marked';
import hljs from 'highlight.js';
import { getDateObject } from '../utils/date-object';
const dir = join('content', 'blog');
marked.use({
renderer: {
code: (code, info, escaped): string => {
const language = hljs.getLanguage(info) ? info : 'plaintext';
const created = hljs.highlight(code, { language }).value;
return `<div class="codeblock"><pre><code class="hljs language-${language}">${created}</code></pre></div>`;
},
},
});
export async function readBlogPosts(
condition?: (body: any) => boolean,
render = true,
includeContent = true
) {
const files = await fs.readdir(dir);
const readMD = files.filter((file) => file.endsWith('.md'));
const readFiles = [];
for (const file of readMD) {
const post = await readBlogPost(
file.replace('.md', ''),
condition,
render,
includeContent
);
if (!post) continue;
readFiles.push(post);
}
readFiles.sort((a, b) =>
new Date(b.date)
.toISOString()
.localeCompare(new Date(a.date).toISOString(), 'en', { numeric: true })
);
return readFiles;
}
export async function readBlogPost(
slug: string,
condition?: (body: any) => boolean,
render = true,
includeContent = true
): Promise<any> {
const decoded = decodeURIComponent(slug);
if (!slug || decoded.includes('/') || decoded.includes('.'))
throw new Error('Invalid post slug');
const file = `${slug}.md`;
const mdpath = join(dir, file);
const read = await fs.readFile(mdpath, { encoding: 'utf-8' });
const { header: parsedHeader, length: headerLength } = await readHeader(read);
const renderedMd = render
? await marked(read.substring(headerLength), { async: true })
: undefined;
const { year, month, day } = getDateObject(parsedHeader);
const content = {
...parsedHeader,
file,
slug,
fullSlug: `${year}/${month}/${day}/${slug}`,
markdown: includeContent ? read.substring(headerLength) : undefined,
html: renderedMd,
};
if (condition) {
if (!condition(content)) {
return;
}
}
return content;
}
export async function getTags() {
const posts = await readBlogPosts(undefined, false, false);
const obj = [];
for (const post of posts) {
for (const tag of post.tags || []) {
const find = obj.find((item) => item.name === tag);
if (find) {
find.count++;
find.posts.push(post.slug);
continue;
}
obj.push({
name: tag,
count: 1,
posts: [post.slug],
});
}
}
return obj;
}
export async function getArchiveTree() {
const posts = await readBlogPosts(undefined, false, false);
const obj = {};
for (const post of posts) {
const { year, month, day } = getDateObject(post);
if (!obj[year]) {
obj[year] = {};
}
if (!obj[year][month]) {
obj[year][month] = {};
}
if (!obj[year][month][day]) {
obj[year][month][day] = [];
}
obj[year][month][day].push(post.slug);
}
return obj;
}
export async function generatePaths(prefix = '/') {
const tree = await getArchiveTree();
const paths = [];
for (const year of Object.keys(tree)) {
paths.push(`${prefix}archive/${year}`);
for (const month of Object.keys(tree[year])) {
paths.push(`${prefix}archive/${year}/${month}`);
for (const day of Object.keys(tree[year][month])) {
paths.push(`${prefix}archive/${year}/${month}/${day}`);
}
}
}
paths.sort((a, b) => b.localeCompare(a, 'en', { numeric: true }));
return paths;
}
async function readHeader(fileContents: string) {
const splitter = fileContents.split('\n');
let header = '';
for (const line of splitter) {
if (line === '---') {
if (!header.length) continue;
break;
}
header += line + '\n';
}
return {
header: yaml.parse(header),
length: header.length + 9,
};
}

7
lib/utils/date-object.ts Normal file
View File

@ -0,0 +1,7 @@
export function getDateObject(post) {
const date = new Date(post.date);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
return { year, month, day };
}

View File

@ -1,3 +1,5 @@
import { generatePaths } from './lib/blog/read-posts';
// https://v3.nuxtjs.org/api/configuration/nuxt.config
export default defineNuxtConfig({
css: ['@/assets/styles/index.scss'],
@ -32,4 +34,10 @@ export default defineNuxtConfig({
],
},
},
hooks: {
async 'nitro:config'(config) {
const routes = await generatePaths('/blog/');
config.prerender.routes.push(...routes);
},
},
});

80
package-lock.json generated
View File

@ -6,11 +6,15 @@
"": {
"hasInstallScript": true,
"dependencies": {
"highlight.js": "^11.6.0",
"sass": "^1.55.0"
},
"devDependencies": {
"@types/marked": "^4.0.7",
"marked": "^4.1.1",
"nuxt": "3.0.0-rc.11",
"prettier": "^2.7.1"
"prettier": "^2.7.1",
"yaml": "^2.1.3"
}
},
"node_modules/@ampproject/remapping": {
@ -1166,6 +1170,12 @@
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"node_modules/@types/marked": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz",
"integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==",
"dev": true
},
"node_modules/@types/node": {
"version": "18.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.0.tgz",
@ -2502,6 +2512,15 @@
"postcss": "^8.2.15"
}
},
"node_modules/cssnano/node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true,
"engines": {
"node": ">= 6"
}
},
"node_modules/csso": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz",
@ -3746,6 +3765,14 @@
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==",
"dev": true
},
"node_modules/highlight.js": {
"version": "11.6.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz",
"integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/hookable": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.4.1.tgz",
@ -4479,6 +4506,18 @@
"semver": "bin/semver.js"
}
},
"node_modules/marked": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz",
"integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==",
"dev": true,
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/mdn-data": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
@ -7480,12 +7519,12 @@
"dev": true
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==",
"dev": true,
"engines": {
"node": ">= 6"
"node": ">= 14"
}
},
"node_modules/yargs": {
@ -8466,6 +8505,12 @@
"integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"@types/marked": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-4.0.7.tgz",
"integrity": "sha512-eEAhnz21CwvKVW+YvRvcTuFKNU9CV1qH+opcgVK3pIMI6YZzDm6gc8o2vHjldFk6MGKt5pueSB7IOpvpx5Qekw==",
"dev": true
},
"@types/node": {
"version": "18.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.0.tgz",
@ -9424,6 +9469,14 @@
"cssnano-preset-default": "^5.2.12",
"lilconfig": "^2.0.3",
"yaml": "^1.10.2"
},
"dependencies": {
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"dev": true
}
}
},
"cssnano-preset-default": {
@ -10301,6 +10354,11 @@
"integrity": "sha512-WdZTbAByD+pHfl/g9QSsBIIwy8IT+EsPiKDs0KNX+zSHhdDLFKdZu0BQHljvO+0QI/BasbMSUa8wYNCZTvhslg==",
"dev": true
},
"highlight.js": {
"version": "11.6.0",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.6.0.tgz",
"integrity": "sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw=="
},
"hookable": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/hookable/-/hookable-5.4.1.tgz",
@ -10860,6 +10918,12 @@
}
}
},
"marked": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.1.1.tgz",
"integrity": "sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==",
"dev": true
},
"mdn-data": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
@ -13072,9 +13136,9 @@
"dev": true
},
"yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.1.3.tgz",
"integrity": "sha512-AacA8nRULjKMX2DvWvOAdBZMOfQlypSFkjcOcu9FalllIDJ1kvlREzcdIZmidQUqqeMv7jorHjq2HlLv/+c2lg==",
"dev": true
},
"yargs": {

View File

@ -8,10 +8,14 @@
"postinstall": "nuxt prepare"
},
"devDependencies": {
"@types/marked": "^4.0.7",
"marked": "^4.1.1",
"nuxt": "3.0.0-rc.11",
"prettier": "^2.7.1"
"prettier": "^2.7.1",
"yaml": "^2.1.3"
},
"dependencies": {
"highlight.js": "^11.6.0",
"sass": "^1.55.0"
}
}

View File

@ -0,0 +1,10 @@
<template>
<NuxtLayout name="blog">
<BlogPost :post="post" :detail="true" />
</NuxtLayout>
</template>
<script setup lang="ts">
const route = useRoute();
const { data: post } = await useFetch(`/api/blog/${route.params.slug}`);
</script>

View File

@ -0,0 +1,14 @@
<template>
<NuxtLayout name="blog">
<template v-for="post of posts">
<BlogPost :post="post" :detail="false" />
</template>
</NuxtLayout>
</template>
<script setup lang="ts">
const route = useRoute();
const { data: posts } = await useFetch(
`/api/blog?year=${route.params.year}&month=${route.params.month}&day=${route.params.day}`
);
</script>

View File

@ -0,0 +1,14 @@
<template>
<NuxtLayout name="blog">
<template v-for="post of posts">
<BlogPost :post="post" :detail="false" />
</template>
</NuxtLayout>
</template>
<script setup lang="ts">
const route = useRoute();
const { data: posts } = await useFetch(
`/api/blog?year=${route.params.year}&month=${route.params.month}`
);
</script>

View File

@ -0,0 +1,12 @@
<template>
<NuxtLayout name="blog">
<template v-for="post of posts">
<BlogPost :post="post" :detail="false" />
</template>
</NuxtLayout>
</template>
<script setup lang="ts">
const route = useRoute();
const { data: posts } = await useFetch(`/api/blog?year=${route.params.year}`);
</script>

11
pages/blog/index.vue Normal file
View File

@ -0,0 +1,11 @@
<template>
<NuxtLayout name="blog">
<template v-for="post of posts">
<BlogPost :post="post" :detail="false" />
</template>
</NuxtLayout>
</template>
<script setup lang="ts">
const { data: posts } = await useFetch('/api/blog');
</script>

10
server/api/blog/[slug].ts Normal file
View File

@ -0,0 +1,10 @@
import { H3Error } from 'h3';
import { readBlogPost } from '~~/lib/blog/read-posts';
export default defineEventHandler(async (event) => {
const slug = getRouterParam(event, 'slug');
try {
const post = await readBlogPost(slug);
return post;
} catch (e) {}
});

View File

@ -0,0 +1,5 @@
import { getArchiveTree } from '~~/lib/blog/read-posts';
export default defineEventHandler(async (event) => {
return getArchiveTree();
});

30
server/api/blog/index.ts Normal file
View File

@ -0,0 +1,30 @@
import { readBlogPosts } from '~~/lib/blog/read-posts';
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const include = (content) => {
const dateObj = new Date(content.date);
if (query.year) {
if (Number(query.year) !== dateObj.getFullYear()) {
return false;
}
}
if (query.month) {
if (Number(query.month) !== dateObj.getMonth() + 1) {
return false;
}
}
if (query.day) {
if (Number(query.day) !== dateObj.getDate()) {
return false;
}
}
return true;
};
return readBlogPosts(include);
});

5
server/api/blog/tags.ts Normal file
View File

@ -0,0 +1,5 @@
import { getTags } from '~~/lib/blog/read-posts';
export default defineEventHandler(async (event) => {
return getTags();
});