Created blog page, many improvements

This commit is contained in:
Andrew Illarionov 2024-02-18 11:30:50 +03:00
parent cfbaf56159
commit 3853e8fa20
23 changed files with 4301 additions and 3284 deletions

View File

@ -1 +1 @@
[{"D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\transitions.scss":"1","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\TransitionY.vue":"2","D:\\Software\\Development\\Websites\\enderman.ch\\index\\app.vue":"3","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\EMail.vue":"4","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\about.vue":"5","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\index.vue":"6","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\projects.vue":"7","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\social.vue":"8","D:\\Software\\Development\\Websites\\enderman.ch\\index\\layouts\\Card.vue":"9","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Options.vue":"10","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Flooter.vue":"11","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\SwipeControls.vue":"12","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Logo.vue":"13","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Route.vue":"14","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\animations\\Portal.vue":"15","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\ui\\Icon.vue":"16"},{"size":165,"mtime":1700688268778,"hashOfConfig":"17"},{"size":1513,"mtime":1700430002840,"hashOfConfig":"17"},{"size":1550,"mtime":1700850776284,"hashOfConfig":"17"},{"size":804,"mtime":1700688952880,"hashOfConfig":"17"},{"size":2135,"mtime":1700689265787,"hashOfConfig":"17"},{"size":3117,"mtime":1700689271383,"hashOfConfig":"17"},{"size":2304,"mtime":1700689265769,"hashOfConfig":"17"},{"size":3232,"mtime":1700689265781,"hashOfConfig":"17"},{"size":1623,"mtime":1700748530633,"hashOfConfig":"17"},{"size":2097,"mtime":1706459320334,"hashOfConfig":"17"},{"size":2045,"mtime":1706266444695,"hashOfConfig":"17"},{"size":1236,"mtime":1706205683132,"hashOfConfig":"17"},{"size":1135,"mtime":1706030243608,"hashOfConfig":"17"},{"size":979,"mtime":1706003786966,"hashOfConfig":"17"},{"size":16911,"mtime":1706027251875,"hashOfConfig":"17"},{"size":935,"mtime":1706445231843,"hashOfConfig":"17"},"5tgxr3"]
[{"D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\transitions.scss":"1","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\TransitionY.vue":"2","D:\\Software\\Development\\Websites\\enderman.ch\\index\\app.vue":"3","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\EMail.vue":"4","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\about.vue":"5","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\index.vue":"6","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\projects.vue":"7","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\social.vue":"8","D:\\Software\\Development\\Websites\\enderman.ch\\index\\layouts\\Card.vue":"9","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Options.vue":"10","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Flooter.vue":"11","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\SwipeControls.vue":"12","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Logo.vue":"13","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\blocks\\Route.vue":"14","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\animations\\Portal.vue":"15","D:\\Software\\Development\\Websites\\enderman.ch\\index\\components\\ui\\Icon.vue":"16","D:\\Software\\Development\\Websites\\enderman.ch\\index\\assets\\styles\\vuetify.scss":"17","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\[...slug].vue":"18","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\index.vue":"19","D:\\Software\\Development\\Websites\\enderman.ch\\index\\pages\\blog\\[slug].vue":"20"},{"size":165,"mtime":1700688268778,"hashOfConfig":"21"},{"size":1513,"mtime":1700430002840,"hashOfConfig":"21"},{"size":1550,"mtime":1700850776284,"hashOfConfig":"21"},{"size":804,"mtime":1700688952880,"hashOfConfig":"21"},{"size":2135,"mtime":1700689265787,"hashOfConfig":"21"},{"size":3117,"mtime":1700689271383,"hashOfConfig":"21"},{"size":2304,"mtime":1700689265769,"hashOfConfig":"21"},{"size":3232,"mtime":1700689265781,"hashOfConfig":"21"},{"size":1623,"mtime":1700748530633,"hashOfConfig":"21"},{"size":2187,"mtime":1706788559308,"hashOfConfig":"22"},{"size":2067,"mtime":1708180203174,"hashOfConfig":"22"},{"size":1236,"mtime":1706205683132,"hashOfConfig":"22"},{"size":1135,"mtime":1706030243608,"hashOfConfig":"22"},{"size":979,"mtime":1706003786966,"hashOfConfig":"22"},{"size":16968,"mtime":1708180277353,"hashOfConfig":"22"},{"size":935,"mtime":1706445231843,"hashOfConfig":"22"},{"size":120,"mtime":1706789613657,"hashOfConfig":"23"},{"size":1988,"mtime":1708191547889,"hashOfConfig":"22"},{"size":2560,"mtime":1708202402381,"hashOfConfig":"22"},{"size":983,"mtime":1708206997656,"hashOfConfig":"22"},"5tgxr3","1nljphs","1lk8nat"]

View File

@ -2,8 +2,6 @@
const config = useAppConfig()
const theme = useVThemeSSR()
console.log('theme.dark.value: ', theme.dark.value)
useHead({
titleTemplate: (chunk?) => {
return chunk === config.title.short

View File

@ -20,6 +20,10 @@
--animate-duration: 1s;
--animate-delay: 0.75s;
--animate-repeat: 1;
--bs-font-sans-serif: 'Lato', 'Times New Roman';
--bs-font-monospace: 'JetBrains Mono', 'Courier New';
--bs-body-font-size: 18px;
--bs-body-line-height: 1.5;
}
// The HTML page.
@ -47,9 +51,6 @@ body {
color: white;
font-family: Lato, sans-serif;
font-size: 18px;
height: 100%;
}
@ -106,6 +107,11 @@ body {
font-weight: 700;
}
.pre-wrap {
white-space: pre-wrap;
word-break: keep-all;
}
// Query-overridable classes.
.background {
background-color: rgb(0 0 0 / 50%);
@ -271,6 +277,73 @@ body {
margin: auto;
}
.post {
&-box {
position: relative;
padding: 0.6942rem 0.6942rem 3rem;
border: 2px solid rgb(153 153 255 / 60%);
background-color: rgb(45 7 110 / 10%);
transition: 0.3s ease;
&:hover {
border-color: rgb(153 153 255 / 100%);
background-color: rgb(45 7 110 / 30%);
}
}
&-pocket {
position: absolute;
bottom: 0;
right: 0;
padding: 0.5rem;
text-transform: uppercase;
background-color: rgb(153 153 255 / 10%);
border-left: 1px solid rgb(153 153 255 / 60%);
border-top: 1px solid rgb(153 153 255 / 60%);
border-top-left-radius: var(--bs-border-radius-xl);
border-bottom-right-radius: var(--bs-border-radius-xl);
}
&-thumb {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
> img {
width: 100%;
height: 280px;
object-fit: cover;
border-radius: 4px;
}
}
&-preamble {
&-thumb {
max-width: 800px;
}
}
&-content {
a {
@extend %link;
padding-left: 0.5rem;
}
}
}
// Dynamic classes.
@include bootstrap.media-breakpoint-up(sm) {
#ender-app {
@ -282,7 +355,7 @@ body {
}
.dimensions {
width: 80%;
width: 90%;
min-height: fit-content;
max-height: 90%;
}
@ -310,23 +383,56 @@ body {
}
@include bootstrap.media-breakpoint-up(md) {
/* TODO: Fill it up :) */
.post {
&-box {
padding-bottom: 0.6942rem;
}
&-thumb {
min-width: 352px;
> img {
max-width: 352px;
height: 198px;
}
}
&-title {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
}
&-description {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
overflow: hidden;
}
}
}
@include bootstrap.media-breakpoint-up(lg) {
.dimensions {
width: 70%;
width: 75%;
}
}
@include bootstrap.media-breakpoint-up(xl) {
.dimensions {
width: 40%;
width: 60%;
}
}
@include bootstrap.media-breakpoint-up(xxl) {
.dimensions {
width: 30%;
width: 50%;
}
}

View File

@ -0,0 +1,6 @@
@use 'vuetify/settings' with (
$utilities: false,
$color-pack: false,
$button-banner-actions-padding: 0 69px,
);

View File

@ -2,6 +2,9 @@
import sky from '~/assets/images/textures/sky.png'
import particles from '~/assets/images/textures/particles.png'
const config = useAppConfig()
const fqdn = config.url.split('//')[1]
const resources: string[] = [sky, particles]
const props = defineProps({
@ -316,7 +319,7 @@ class Portal {
notice() {
console.log(
`%c \u2587%c\u2587%c\u2587 %c\u2587%c\u2587%c\u2587 %c // ECMAPortal v${this.version} by Endermanch & WiPet\n\n\thttps://enderman.ch\n\thttps://go.enderman.ch/wipet`,
`%c \u2587%c\u2587%c\u2587 %c\u2587%c\u2587%c\u2587 %c // ECMAPortal v${this.version} by Endermanch & WiPet\n\n\t${config.url}\n\t${config.shortener}/wipet`,
'color: #E58EFF',
'color: #D52DFF',
'color: #E58EFF',
@ -327,7 +330,7 @@ class Portal {
)
console.log(
'If any errors occur below, please send a screenshot of them my way!\n\t%ccontact@enderman.ch',
`If any errors occur below, please send a screenshot of them my way!\n\t%ccontact@${fqdn}`,
'color: #87CEFA',
)
}

View File

@ -4,9 +4,10 @@ const props = defineProps({
})
const config = useAppConfig()
const fqdn = config.url.split('//')[1]
const currentYear = new Date().getFullYear()
const mailTemplate = `I've just found a bug on https://enderman.ch and would like to report it.%0D%0A%0D%0AWebsite version: ${
const mailTemplate = `I've just found a bug on ${config.url} and would like to report it.%0D%0A%0D%0AWebsite version: ${
config.build.version
}%0D%0ABuild date: ${
config.build.date
@ -29,9 +30,9 @@ const mailTemplate = `I've just found a bug on https://enderman.ch and would lik
'link-hi-force-dark': !props.opaque,
'link-hi': props.opaque,
}"
address="contact@enderman.ch"
cc="admin@enderman.ch"
subject="Bug report: enderman.ch"
:address="`contact@${fqdn}`"
:cc="`admin@${fqdn}`"
:subject="`Bug report: ${fqdn}`"
:body="mailTemplate"
>
<strong>Report a bug</strong>
@ -43,7 +44,7 @@ const mailTemplate = `I've just found a bug on https://enderman.ch and would lik
'link-hi-force-dark': !props.opaque,
'link-hi': props.opaque,
}"
href="https://enderman.ch"
:href="config.url"
>Enderman</a
>. All rights reserved.
<wbr />

View File

@ -1,6 +1,8 @@
<script setup lang="ts">
import cogIcon from '~/assets/images/icons/cog.png'
import pearlIcon from '~/assets/images/icons/pearl.gif'
const theme = useVThemeSSR()
</script>
<template>
@ -46,13 +48,13 @@ import pearlIcon from '~/assets/images/icons/pearl.gif'
</div>
</div>
</div>
Report a bug
</VCardText>
<VCardActions>
<VSpacer></VSpacer>
<VBtn color="danger" @click="theme.toggle()" />
<VBtn
color="success"
variant="outlined"
prepend-icon="close"
@click="isActive.value = false"
>

View File

@ -22,6 +22,7 @@ interface ThemeConfig {
type config = {
url: string
shortener: string
name: string
title: TitleConfig
description: string
@ -32,6 +33,7 @@ type config = {
export default {
url: 'https://enderman.ch',
shortener: 'https://go.enderman.ch',
name: packageJSON.name || 'app',
title: {
full: "Enderman's Website",
@ -47,7 +49,7 @@ export default {
theme: {
file: './assets/styles/vuetify.scss',
cookie: 'color-scheme',
default: 'vanilla',
default: 'chocolate',
light: 'vanilla',
dark: 'chocolate',
themes: {

View File

@ -0,0 +1,13 @@
---
title: Hello World!
description: I created this post to test @nuxt/content.
created: 2023-08-12T16:40:09Z
updated: 2024-02-18T20:31:54Z
draft: false
tags: ['hello', 'world']
thumbnail: '/images/blog/thumbnails/hello.jpg'
---
# Hello!
This is an example piece of content

View File

@ -0,0 +1,344 @@
---
title: The Windows Context Menu Is It a Lost Cause?
description: We dive in deep into the Windows context menu and how to customize it to your liking.
created: 2023-08-13T15:51:19Z
updated: 2024-02-18T21:32:12Z
draft: false
tags: ['windows', 'context-menu']
thumbnail: '/images/blog/thumbnails/the-windows-context-menu.png'
---
#### The Windows context menu is... poorly done.
And third-party software developers adding oil into the dumpster fire aren't helping with it. It's not their fault, though. There's no common standard and almost no documentation for adding entries into the context menu.
At this point, there's no return and no salvation for it. At very least it's working. What actually doesn't help is that Microsoft continued with their all time classic - piling up layers upon layers of indirection with their all new Windows 11 context menu, which is also poorly done and even more enclosed in terms of the API.
I want to dedicate this blog post to _actually customizing_ the Windows 10/11 Legacy context menu to your liking instead of simply complaining about its inner workings. Using methods described in this article, you will be able to bring your menu from this abomination...
![Before](/images/blog/assets/the-windows-context-menu/before.png)
**...to this!**
![After](/images/blog/assets/the-windows-context-menu/after.png)
Be aware - there's _plenty_ of registry digging involved. And the truly worst part is...
**Forget any logic when dealing with registry keys.** The idea of registry's structural integrity has long since been abandoned and nobody seems to care about it as, well, the end user is never going to care about it, and if they do - they know what they're doing and they will not complain.
While there are layers of abstraction applied to the most Windows settings, the context menu is, unfortunately, left aside. And much to our dismay, its backend is also janky, terrible and tangled up.
The only fair question to reader from me is "Do you really believe it's worth your time?". And if your answer is yes, and you're as much of a perfectionist as I am, buckle up and get ready for upcoming few hours of pure joy.
Please note that any sfc /scannow or dism /restorehealth invocations will probably interfere with your context menu as Microsoft are really weird - they seem to allow customization of it, but still don't fully support it. It's been like this for at least 20 years.
Shell entries & Context Menu handlers
There are two main ways to insert entries into the context menu:
Shell entries
Context Menu handlers
Both of them are very poorly documented, but the idea is very simple: whereas Shell entries are managed by the registry, the Context Menu handlers are managed by the software in question.
Shell entries are much, much better in terms of flexibility and customization (in fact, you don't get any customization with Context Menu handlers at all, unless the developer was nice enough to let you tweak context menu settings through their app or provide necessary documentation for registry endpoints). 7-Zip would serve as a neat example of good software design, as the creator allows you to tweak settings of their handler.
![7-Zip Context Menu Settings.png](/images/blog/assets/the-windows-context-menu/7-zip-context-menu-settings.png)
MobaXterm would also serve as a nice example for the case, where shell entries are used, but you can tweak them from the app itself.
MobaXterm Context Menu Settings.png
And so, there are 3 types of applications in terms of their context menu customization-friendliness:
Context Menu handler apps with in-app settings (the easiest)
Apps using shell entries (customizable via registry hacks or from within software)
Context Menu handler apps without anything (uneditable entries)
The first case is the simplest - you just have to consult the documentation of software you're using to find out whether it supports context menu customization or not.
The other two cases is what I'm going to tackle in this write-up.
For starters, all modifications are done under HKEY_CLASSES_ROOT (the HKEY_LOCAL_MACHINE\Software\Classes alias). That hive contains data about shell components and file extensions. For example, any data under exefile will apply to all executable files. That's one of the methods companion viruses utilized back in the day to accompany their victims - the .exe/.com files. Either way, since the HKCR hive is basically divided into file/shell extensions, we will have to edit context menu for each file and shell extension manually. Yeah. This is absolutely abysmal. There is no better way to approach this. And while you might already be reconsidering your choices, let me teach you the basics so you could at least bring your most used extensions and most noticeable context menu areas up to par... Some generalizations are also made within the registry. For instance, there are wildcard entries for ALL files, or for ALL images and so on. That still does not excuse the horrible approach Microsoft has taken with the file extensions from the beginning.
Even though the incompleteness always annoyed me, unfortunately, that's how sometimes life is.
This is how the general structure of an extension looks like:
EXE File Registry Structure.png
Editable entries are stored under the Shell key. Uneditable/highly customizable (thanks to the developer) entires are stored under ShellEx. Capitalization plays no role, the Windows registry key names is case-insensitive. The value names, however, are. What a nice spec!
Sort of editable entries?
The structure varies from extension to extension, but you know you've found it when you see the Shell key. Subkeys of it represent the "shell entries" - or, well, customizable entries of the context menu.
Directory.png
Each shell entry contains the command subkey, default value of which controls what executes upon clicking on the context menu entry.
The default entry follows the Batch syntax, albeit very poorly. You cannot have extended Batch logic in the entries, which made me immeasurably sad and also made me write utilities called quiet and elevate for that specific purpose. No matter how hard I tried, I could not find the spec for the extents of the Batch logic in the default value for command.
Shell Entry Structure.png
Command Verb.png
In general, I'd suggest you to treat the command value as the raw command line, but with the %1 macro for the full input file path without quotation marks (e.g. if you right-click C:\Users\bleh\p.txt, and click on your own shell entry, the %1 within command will be equivalent to C:\Users\bleh\p.txt) and %V for the full directory path. That must be one of the few values to be consistent across all the shell entries.
Sometimes command's default value is left unused for DelegateExecute to take its place.
If that's the case, that means the command handler is implemented as an IExecuteCommand COM object, or, well, you can't really customize it. All it contains is the UUID of the read-only object.
DelegateExecute.png
Adding entries
Locate the extension (be it a file, or a shell one) you want to target.
Careful! Some extensions can be buried within other extensions. For example, the folder background extension is located inside the folder extension under the Background key.
Navigate to the shell key. If there is none and you're confident there should be one (by feel!), try creating one (it's highly likely not going to affect the system stability).
Create a new entry, you can call it whatever you want, but it should be alphanumeric.
Create a command key.
Specify the command line you want to execute upon the click of your entry.
The entry should appear in the context menu. If it does not, restart Explorer. If it still doesn't appear, you've probably messed up the procedure or the extension isn't registered in Windows.
Nesting entries
You can nest entries too! Repeat the first 4 steps from The Windows Context Menu > Adding entries, but don't add the command key yet. Instead, create another shell key, and then inside of it create a key not called command. And working in that new key, repeat last 3 steps. Finally, get back to your initial ancestor entry and add an empty REG_SZ value called SubCommands.
Nested Entries Structure.png
SubCommands.png
You can make an entry have only a single nested entry as well, I've tested it.
Nested Entries.png
To control the parent context menu entry, use the outer Shell entry subkey.
Nested Entries Order.png
To control its children, use the inner Shell entry subkeys.
Nested Entries Order 2.png
Editing entries
Now you're probably wondering how to name the entries and add nice little icons next to them.
By default, the context menu entries duplicate their names from their respective shell subkeys.
Name
To override the default name, you can use either the (Default) or MUIVerb REG_SZ values inside the respective shell entry (not command). MUIVerb takes precedence over the default value.
You can put in anything you want, it's going to work.
Icon
To enable the icon for your shell entry, you should use the Icon REG_SZ value inside the respective shell entry. It uses that weird WinAPI resource syntax to create a direct link to the icon.
PATH_TO_FILE_WITH_ICON_RESOURCES,INDEX
If you want to select the first icon (in that case, the INDEX part is 0), the INDEX part can be omitted completely, as in:
PATH_TO_FILE_WITH_ICON_RESOURCES
For example, the following expressions are completely valid and are going to work:
"C:\Windows\system32\cmd.exe"
C:\Windows\regedit.exe
"C:\Windows\explorer.exe",0
C:\Windows\system32\cmd.exe,0
cmd.exe
shell32.dll,3
SHELL32.DLL,10
"C:\Windows\system32\SHELL32.DLL",9
You can omit the quotes and full paths depending on the %PATH% variable that unholy concoction of an API uses. Only God knows what paths it's able to parse by default. C:\Windows, C:\Windows\system32 work for sure, though.
I still highly suggest supplying the full path to file, as even the %PATH%s of the Explorer's icon picker and the Icon value do not seem to correspond.
Tip: you can use the Explorer folder icon picker to acquire the icon DLL and calculate the offset of the desired icon. For example, the one I've selected in the screenshot has an index of 5.
Indexing begins with 0 and ascends in columns.
Icon Viewer.png
You can also pull resource strings from files the same way for MUI Verbs and default values.
DLL Syntax.png
Separators
Another integral part of the context menu, which is hardly documented by anyone. Managing them is also rather quirky, but you get used to it after some time.
Separators in shell entries are relative to the element you're enabling them for and can overlap without any issue.
Use SeparatorBefore and SeparatorAfter empty REG_SZ values to add a separator before or after the entry you're editing, respectively.
Separators.png
Appear in the Shift+Click context menu only
You can make your shell entry be "hidden" from a normal context menu click. Such entry is called "extended". It's possible to enable that functionality by creating an empty REG_SZ value called Extended inside the respective shell entry.
Position override
See The Windows Context Menu > Sorting entries > Position override
Hide in Safe Mode
To hide the context menu entry in safe mode, create an empty REG_SZ value called HideInSafeMode inside the respective shell entry.
Never default
An example of a default entry would be "Open" for folders, or generally the bold context menu entry shown at the top. That's the entry Windows will default to when you open the file with a certain extension.
Media.png
There can be no default entry at all, and that's perfectly fine. If you don't want your entry to show up as a default one, add an empty REG_SZ value called NeverDefault into the respective shell entry.
Pass through to Context Menu handlers
If you know what context handler you want to invoke on shell entry click, you can specify a so-called Verb Handler.
Create a new REG_SZ value VerbHandler and specify the context menu handler's UUID as data.
Context Menu Handler 1.png
Context Menu Handler 2.png
Other options
I am not ashamed to admit that I have absolutely zero idea what some of the options do, even though they might sound obvious.
If you do have the knowledge, please contact me on Twitter: @endermanch or send a mail my way: blogs@enderman.ch
For example:
NoWorkingDirectory - could be hiding the working directory from the app it's starting?
CanonicalName
CommandStateHandler
CommandStateSync
Description
VerbName
Deleting entries
Pretty much the opposite of adding one.
Locate the extension (be it a file, or a shell one) you want to target.
Sometimes multiple locations can correspond to the same place in the context menu, which might result in the entry remaining in place, even though you've deleted it.
Navigate to the shell key.
Find the shell entry you want to delete. You can find out the application behind it by looking into the default value of the command key.
Delete the key.
Sorting entries
Now that's the funniest one. I even memed about it on Twitter.
The only way to sort the context menu is via the alphabetical order of its respective shell entries in the registry. Yes. I am not kidding. Let the screenshots speak for themselves.
Alphabetical Sort 1.png
Alphabetical Sort 2.png
Yeah... I'd rather not comment that.
Position override
Another silly and janky feature that has a limited amount of use cases. You can manually override the position of your context menu item to be... either at the very top, or the very bottom. Not much better than alphabetical sorting if you ask me...
In your entry under shell, create a REG_SZ value called Position.
If you want to force the context menu handler to the top, put Top as data, otherwise put Bottom.
Sometimes this may be necessary, as, for example, Microsoft dumps their regular alphabetical sorting strategy at the bottom of the context menu, where display and personalization settings reside. They simply specify the Bottom position for each of the entries. If I recall correctly, they're still sorted alphabetically between themselves, as position override takes precedence.
Position Override.png
Bottom line
Some of the default shell entries like Powershell have specific keys (for the sake of that example, ProgrammaticAccessOnly) to control their appearance context menu.
As a rule of thumb, every time you're dealing with existing shell entries, I REALLY suggest you to search for a way to modify them online before resorting to any of the general cases I've described above. If you can't find anything even on the Internet, well, anything works.
Uneditable entries
As for the Context Menu handlers, there's actually not much you can do. They're located under the shellex\ContextMenuHandlers subkey.
Context Menu Handler 3.png
Most of the times, the only data they contain is a lonely UUID. That doesn't help much.
Context Menu Handler 4.png
So, to remove the Context Menu handler, you should just delete the subkey under ContextMenuHandlers. I strongly suggest you save the UUID it contained so that you could roll back at any time later.
Context Menu Handler 5.png
Adding your own... Well, that's complicated. You actually have to write your own handler in a language supporting Windows API or any sort of an abstraction of it. For example, C# is a decent choice. I don't suggest you waste time on this, unless you're making full-on software that you want to make more accessible to Windows users.
Dealing with locked keys
If the registry key is locked and you don't have write permissions for it, it's owned by TrustedInstaller and you have to manually reclaim ownership of the key and allow yourself to make edits to it.
Optimal way: Using Process Hacker with the TrustedInstaller plugin or Winaero Tweaker, run regedit as Trusted Installer and perform necessary tweaks.
General solution:
Navigate to the key you want to take ownership of
Open the Properties window
Head over to the Advanced tab
At the top of the Advanced Security Settings it states Owner: TrustedInstaller. Click Change
Input your username into the Enter the object name to select (examples) text area.
Click OK
Check the Replace owner on subcontainers and objects checkbox
Check the Replace all child object permission entries checkbox
Click OK
Select Administrators (PC_NAME\Administrators) in the group selector and then check Full Control box in the Permissions window down below
Click OK
After performing above steps, you must have gotten full and almost complete (some registry values and keys are still protected after the general procedure) access to the desired key.
Registry paths involved
Bottom line of desktop:
Computer\HKEY_CLASSES_ROOT\DesktopBackground
Desktop Background.png
Folder Background
Computer\HKEY_CLASSES_ROOT\Directory\Background\shell
Directory Background.png
Folder
Computer\HKEY_CLASSES_ROOT\Directory\shell
Folder.png
Folder Item (extends Folder)
Computer\HKEY_CLASSES_ROOT\Folder
File Item.png
Drive
Computer\HKEY_CLASSES_ROOT\Drive
Drive.png
Library folder
Computer\HKEY_CLASSES_ROOT\LibraryFolder
Library Item.png
Library background inherits from Folder Background, but can't parse the %V macro, which results in the following error. This is Microsoft's fault. To my knowledge, there is no way to fix that.
Library Background.png
Explorer Error.png
All Files
Computer\HKEY_CLASSES_ROOT\*
All Files.png
All Filesystem Objects (yep, that's different from All Files - it's a generalization that includes drives and symbols)
Computer\HKEY_CLASSES_ROOT\AllFilesystemObjects
All Filesystem Objects.png
Text Files
Computer\HKEY_CLASSES_ROOT\SystemFileAssociations\text
Image Files
Computer\HKEY_CLASSES_ROOT\SystemFileAssociations\image
Document Files
Computer\HKEY_CLASSES_ROOT\SystemFileAssociations\document
Audio Files
Computer\HKEY_CLASSES_ROOT\SystemFileAssociations\audio
Video Files
Computer\HKEY_CLASSES_ROOT\SystemFileAssociations\video
Cleaning up the context menu
Most obnoxious default entries can be cleaned up using Winaero Tweaker. I suggest you use it to make the process way more bearable for yourself.
Winaero 1.png
Winaero 2.png
Areas Winaero Tweaker does not reach
Remove "Open PowerShell window here"
Navigate to Computer\HKEY_CLASSES_ROOT\Directory\shell\Powershell
Unlock key (The Windows Context Menu > Dealing with locked keys)
Create a new REG_SZ value ProgrammaticAccessOnly without any data
Navigate to Computer\HKEY_CLASSES_ROOT\Directory\Background\shell\Powershell
Unlock key
Create a new REG_SZ value ProgrammaticAccessOnly without any data
Remove "Open Linux shell here"
Navigate to Computer\HKEY_CLASSES_ROOT\Directory\shell\WSL
Unlock key
Delete WSL key altogether
Navigate to Computer\HKEY_CLASSES_ROOT\Directory\Background\shell\Powershell
Unlock key
Create a new REG_SZ value ProgrammaticAccessOnly without any data
Edit the "Edit" text context menu command
Navigate to Computer\HKEY_CLASSES_ROOT\SystemFileAssociations\text\shell\edit
Make sure you've read the article thoroughly, then do anything you want with it.
Bottom line
No matter how terrible the context menu API might be for Windows, there's still a way to customize it. Hope you found the tricks useful!
Contact
If you've spotted a mistake (be it orthographical, factual or any other) in this article or want to add
And third-party software developers adding oil into the dumpster fire aren't helping with it. It's not their fault, though. There's no common standard and almost no documentation for adding entries into the context menu.

View File

@ -49,8 +49,6 @@ const aliases = <IconAliases>{
const iconify = <IconSet>{
component: (props: any) => {
console.log(props)
const { icon, tag, ...rest } = props
const strIcon = icon as string

View File

@ -5,7 +5,7 @@ export default defineNuxtConfig({
app: {
head: {
htmlAttrs: {
lang: config.locale,
lang: config.locale || 'en',
},
link: [
{
@ -49,7 +49,6 @@ export default defineNuxtConfig({
['@nuxtjs/eslint-module', { failOnError: false, lintOnStart: false }],
['@nuxtjs/stylelint-module', { failOnError: true, lintOnStart: false }],
'@pinia/nuxt',
'@nuxtjs/strapi',
'@nuxt/content',
'vuetify-nuxt-module',
'@nuxtjs/google-fonts',
@ -59,6 +58,11 @@ export default defineNuxtConfig({
'nuxt-og-image',
'nuxt-link-checker',
],
content: {
markdown: {
remarkPlugins: ['remark-reading-time'],
},
},
nitro: {
prerender: {
crawlLinks: true,
@ -107,11 +111,8 @@ export default defineNuxtConfig({
defaultSet: 'custom',
},
defaults: {
VApp: {
class: 'ender-app',
},
VBtn: {
class: 'text-none',
style: 'text-transform: none; letter-spacing: normal;',
},
},
},

6759
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,8 +21,7 @@
"@nuxtjs/eslint-config-typescript": "^12.1.0",
"@nuxtjs/eslint-module": "^4.1.0",
"@nuxtjs/google-fonts": "^3.1.3",
"@nuxtjs/strapi": "^1.11.0",
"@nuxtjs/stylelint-module": "^5.1.0",
"@nuxtjs/stylelint-module": "^5.2.0",
"@pinia/nuxt": "^0.5.1",
"@typescript-eslint/parser": "^6.19.1",
"animate.css": "latest",
@ -33,7 +32,7 @@
"eslint-plugin-nuxt": "^4.0.0",
"eslint-plugin-prettier": "^5.1.3",
"iconify-icon": "^1.0.8",
"nuxt": "^3.9.3",
"nuxt": "^3.10.2",
"nuxt-link-checker": "^3.0.0-rc.6",
"nuxt-og-image": "^3.0.0-rc.29",
"nuxt-simple-robots": "^4.0.0-rc.13",
@ -42,9 +41,9 @@
"pinia": "^2.1.7",
"prettier": "^3.2.4",
"sass": "^1.70.0",
"stylelint": "^15.11.0",
"stylelint": "^16.2.1",
"stylelint-config-recommended-vue": "^1.5.0",
"stylelint-config-standard-scss": "^11.0.0",
"stylelint-config-standard-scss": "^13.0.0",
"typescript": "^5.3.3",
"vue": "^3.4.15",
"vue-router": "^4.2.5",
@ -54,6 +53,7 @@
},
"dependencies": {
"@date-io/date-fns": "^3.0.0",
"date-fns": "^3.3.1"
"date-fns": "^3.3.1",
"remark-reading-time": "^2.0.1"
}
}

92
pages/[...slug].vue Normal file
View File

@ -0,0 +1,92 @@
<script setup lang="ts">
const config = useAppConfig()
const meta = {
title: 'Page not found',
description:
'The page you are looking for does not exist. It might have been removed, had its name changed, or is temporarily unavailable.',
image: `${config.url}/images/logo.png`,
url: config.url,
}
useSeoMeta({
title: meta.title,
description: meta.description,
ogTitle: meta.title,
ogDescription: meta.description,
ogImage: meta.image,
ogUrl: meta.url,
ogType: 'website',
twitterTitle: meta.title,
twitterDescription: meta.description,
twitterImage: meta.image,
twitterCard: 'summary',
})
useHead({
title: 'Page not found',
htmlAttrs: {
lang: config.locale || 'en',
},
link: [
{
rel: 'icon',
type: 'image/x-icon',
href: '/favicon.ico',
},
],
})
const error = {
http_code: 200,
title: config.title,
url: config.url,
locale: config.locale || 'en',
data: {
path: '/error.vue',
content: {
title: 'Page not found!',
description:
"The page you are looking for does not exist. However, no 404 error has occurred, as you're browsing through a web application loaded into memory and HTTP error codes make no sense here.",
},
},
slug: useRoute().params.slug,
build: config.build,
}
const errorString = JSON.stringify(error, null, 2)
const buffer = ref('')
const startTime = Date.now()
const dt = 20
let i = 0
function type() {
if (Date.now() > startTime + dt * i) buffer.value += errorString[i++]
if (i < errorString.length) requestAnimationFrame(type)
}
onMounted(() => setTimeout(type, 1500))
</script>
<template>
<div>
<h3 class="alex">Page not found!</h3>
<pre class="pre-wrap">{{ buffer }}<span class="blinker">&#9610;</span></pre>
</div>
</template>
<style scoped lang="scss">
.blinker {
animation: blinker 1s steps(2, start) infinite;
}
@keyframes blinker {
from {
visibility: visible;
}
to {
visibility: hidden;
}
}
</style>

View File

@ -1,4 +1,5 @@
<script setup lang="ts">
const config = useAppConfig()
const age =
new Date(Number(new Date()) - Number(new Date('2003-09-18'))).getFullYear() -
1970
@ -6,8 +7,8 @@ const age =
const meta = {
title: 'About Me',
description: `Nice to meet you!~ I'm Andrew, a ${age}-year-old developer from Kaluga, Russia.`,
image: 'https://enderman.ch/images/logo.png',
url: 'https://enderman.ch/about',
image: `${config.url}/images/logo.png`,
url: `${config.url}/about`,
}
useSeoMeta({
@ -27,7 +28,7 @@ useSeoMeta({
useHead({
title: 'About',
htmlAttrs: {
lang: 'en',
lang: config.locale || 'en',
},
link: [
{
@ -82,6 +83,16 @@ useHead({
thinking for quite some time, and then concluded that adding «ch» at the
end sounded pretty good. I was 14 at the time of making that decision.
</li>
<li>
<strong
>When will you make a new video? What happened to your
schedule?</strong
>
</li>
<li>
One day. Haven't been particularly motivated lately, but life's busy. 🙁
I'm alive and well, though.
</li>
</ul>
</section>
</template>

38
pages/blog/[slug].vue Normal file
View File

@ -0,0 +1,38 @@
<script setup lang="ts">
const { slug } = useRoute().params
</script>
<template>
<article
class="scrollbar fade-mask-sm d-flex flex-column gap-3 flex-grow-1 overflow-x-hidden overflow-y-auto py-sm-4 pe-sm-3"
>
<ContentDoc :path="`/blog/${slug}`">
<template #default="{ doc }">
<div class="post-preamble">
<h3 class="alex">{{ doc.title }}</h3>
<img
draggable="false"
:src="doc.thumbnail"
:alt="doc.title"
class="post-preamble-thumb rounded-4"
/>
</div>
<div class="post-content">
<ContentRenderer :value="doc" />
</div>
</template>
<template #not-found>
<div
class="d-flex flex-column justify-content-center align-items-center gap-3"
>
<span>Blog post not found 🙁</span>
<NuxtLink to="/blog" class="link-hi">Back to blog</NuxtLink>
</div>
</template>
</ContentDoc>
</article>
</template>
<style scoped lang="scss"></style>

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

@ -0,0 +1,80 @@
<script setup lang="ts">
import { formatDate } from 'date-fns'
</script>
<template>
<section>
<h3 class="alex">Recent posts</h3>
<ContentList
:query="{
path: '/blog',
where: [{ draft: false }],
limit: 3,
sort: [{ created: -1 }],
}"
>
<template #default="{ list }">
<div class="d-grid gap-3">
<NuxtLink
v-for="post in list"
:key="post._path"
:to="post._path"
class="no-decoration"
>
<article
class="post-box d-flex flex-column flex-md-row gap-3 rounded-4 h-100"
>
<div class="post-thumb">
<img
draggable="false"
:src="post.thumbnail"
:alt="post.title"
class="rounded-4"
/>
</div>
<div>
<h3 class="post-title alex">{{ post.title }}</h3>
<p class="post-description">{{ post.description }}</p>
<div class="row">
<div class="col-6 d-flex flex-row gap-1 align-items-center">
<iconify-icon icon="mdi:calendar" />
<small class="nobr">
{{ formatDate(post.created, 'LLLL do, y &ndash; HH:mm') }}
</small>
</div>
</div>
<div v-if="post.updated" class="row">
<div class="col-12 d-flex flex-row gap-1 align-items-center">
<iconify-icon icon="mdi:pencil" />
<small class="nobr">
{{ formatDate(post.updated, 'LLLL do, y &ndash; HH:mm') }}
</small>
</div>
</div>
</div>
<div class="post-pocket d-flex flex-row gap-1 align-items-center">
<iconify-icon icon="mdi:clock-outline" />
<small class="nobr font-monospace font-small">
{{ post.readingTime.text }}
</small>
</div>
</article>
</NuxtLink>
</div>
</template>
<template #not-found>
<div
class="d-flex flex-column justify-content-center align-items-center gap-3"
>
<span>No posts found 🙁</span>
<NuxtLink to="/" class="link-hi">Back to index</NuxtLink>
</div>
</template>
</ContentList>
</section>
</template>
<style scoped lang="scss"></style>

View File

@ -3,7 +3,7 @@ const config = useAppConfig()
const meta = {
title: 'Enderman',
description: config.description,
image: 'https://enderman.ch/images/logo.png',
image: `${config.url}/images/logo.png`,
url: config.url,
}
@ -43,9 +43,9 @@ useHead({
I'm <strong>Enderman</strong> &ndash; a software engineer, a malware
enthusiast and most importantly, a weird tall creature. I have over 300K
subscribers on
<a class="link-hi" href="https://go.enderman.ch/youtube">YouTube</a> and
<a class="link-hi" :href="`${config.shortener}/youtube`">YouTube</a> and
over 20K followers on
<a class="link-hi" href="https://go.enderman.ch/twitter">Twitter</a>.
<a class="link-hi" :href="`${config.shortener}/twitter`">Twitter</a>.
Sometimes I wish there were 48 hours in a day.
</p>
<div class="row g-3">
@ -57,7 +57,7 @@ useHead({
<strong>C/C++</strong> for desktop applications and any common
backend stack with framework-loaded TypeScript for web. You can take
a look at my code on
<a class="link-hi" href="https://go.enderman.ch/github">GitHub</a>.
<a class="link-hi" :href="`${config.shortener}/github`">GitHub</a>.
</li>
<li>
I have the most unnecessary, yet fascinating knowledge about
@ -70,7 +70,7 @@ useHead({
<li>
I research and analyze modern malware, educate computer users about
it and preserve history. The repository can be found
<a class="link-hi" href="https://go.enderman.ch/repository">here</a
<a class="link-hi" :href="`${config.shortener}/repository`">here</a
>.
</li>
<li>I make videos for you to enjoy!</li>
@ -86,7 +86,7 @@ useHead({
<li>Philosophy</li>
<li>Geopolitics</li>
<li>
<a class="link-hi" href="https://go.enderman.ch/chess">Chess</a>
<a class="link-hi" :href="`${config.shortener}/chess`">Chess</a>
</li>
<li>Solitude</li>
</ul>

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
// AI-Generated test data.
const config = useAppConfig()
const projects = [
{
name: 'MalwareWatch',
@ -25,7 +25,7 @@ const projects = [
{
name: 'Windows XP Keygen / UMSKT',
description: 'A key generator for Windows XP.',
url: 'https://go.enderman.ch/xpkeygen',
url: `${config.shortener}/xpkeygen`,
},
]
@ -33,8 +33,8 @@ const meta = {
title: "Enderman's Projects",
description:
"A display of my largest projects. Some of them aren't published on YouTube.",
image: 'https://enderman.ch/images/logo.png',
url: 'https://enderman.ch/projects',
image: `${config.url}/images/logo.png`,
url: `${config.url}/projects`,
}
useSeoMeta({
@ -54,7 +54,7 @@ useSeoMeta({
useHead({
title: 'Projects',
htmlAttrs: {
lang: 'en',
lang: config.locale || 'en',
},
link: [
{

View File

@ -1,8 +1,11 @@
<script setup lang="ts">
const config = useAppConfig()
const fqdn = config.url.split('//')[1]
const socials = [
{
name: 'YouTube',
url: 'https://go.enderman.ch/youtube',
url: `${config.shortener}/youtube`,
icon: {
name: 'logos:youtube-icon',
color: 'white',
@ -10,7 +13,7 @@ const socials = [
},
{
name: 'Andrew',
url: 'https://go.enderman.ch/andrew',
url: `${config.shortener}/andrew`,
icon: {
name: 'logos:youtube-icon',
color: 'white',
@ -18,7 +21,7 @@ const socials = [
},
{
name: 'Twitch',
url: 'https://go.enderman.ch/twitch',
url: `${config.shortener}/twitch`,
icon: {
name: 'fa:twitch',
color: '#B68CFF',
@ -26,7 +29,7 @@ const socials = [
},
{
name: 'TikTok',
url: 'https://go.enderman.ch/tiktok',
url: `${config.shortener}/tiktok`,
icon: {
name: 'logos:tiktok-icon',
color: 'white',
@ -34,7 +37,7 @@ const socials = [
},
{
name: 'Twitter',
url: 'https://go.enderman.ch/twitter',
url: `${config.shortener}/twitter`,
icon: {
name: 'logos:twitter',
color: 'white',
@ -42,7 +45,7 @@ const socials = [
},
{
name: 'Telegram',
url: 'https://go.enderman.ch/telegram',
url: `${config.shortener}/telegram`,
icon: {
name: 'logos:telegram',
color: 'white',
@ -50,7 +53,7 @@ const socials = [
},
{
name: 'Discord',
url: 'https://go.enderman.ch/discord',
url: `${config.shortener}/discord`,
icon: {
name: 'logos:discord-icon',
color: 'white',
@ -58,7 +61,7 @@ const socials = [
},
{
name: 'GitHub',
url: 'https://go.enderman.ch/github',
url: `${config.shortener}/github`,
icon: {
name: 'fa:github',
color: 'white',
@ -66,7 +69,7 @@ const socials = [
},
{
name: 'Steam',
url: 'https://go.enderman.ch/steam',
url: `${config.shortener}/steam`,
icon: {
name: 'fa:steam',
color: 'white',
@ -78,8 +81,8 @@ const meta = {
title: 'Enderman Online',
description:
'Discover me on your favorite social media and find ways to contact me here.',
image: 'https://enderman.ch/images/logo.png',
url: 'https://enderman.ch/projects',
image: `${config.url}/images/logo.png`,
url: `${config.url}/projects`,
}
useSeoMeta({
@ -99,7 +102,7 @@ useSeoMeta({
useHead({
title: 'Socials',
htmlAttrs: {
lang: 'en',
lang: config.locale || 'en',
},
link: [
{
@ -140,11 +143,11 @@ useHead({
Personal:
<EMail
class="link-hi"
address="contact@enderman.ch"
:address="`contact@${fqdn}`"
subject="Hey Enderman!"
/><br />
Manager: <EMail class="link-hi" address="manager@enderman.ch" /><br />
Abuse: <EMail class="link-hi" address="abuse@enderman.ch" />
Manager: <EMail class="link-hi" :address="`manager@${fqdn}`" /><br />
Abuse: <EMail class="link-hi" :address="`abuse@${fqdn}`" />
</p>
</div>
</div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB