This commit is contained in:
Fedor Katurov 2022-11-02 11:58:45 +06:00
commit 5104c2518b
34 changed files with 6844 additions and 0 deletions

View file

@ -0,0 +1,35 @@
<script lang="ts" setup>
interface Props {
href?: string;
blank?: boolean;
}
withDefaults(defineProps<Props>(), {
href: "",
blank: false,
});
const isInternalLink = (link: string) => !link.match(/^\w+\:\/\//);
const transformInternalLinks = (href: string) => {
if (!isInternalLink(href)) {
return href;
}
return href
.toLowerCase()
.replaceAll("%20", " ")
.replace(/\d+/g, "")
.trim()
.replaceAll(" ", "-");
};
</script>
<template>
<NuxtLink
:href="transformInternalLinks(href)"
:target="isInternalLink(href) ? '' : '_blank'"
>
<slot
/></NuxtLink>
</template>

View file

@ -0,0 +1,81 @@
<script lang="ts" setup>
interface Props {
code?: string;
language?: string | null;
filename?: string | null;
highlights?: number[];
}
const props = withDefaults(defineProps<Props>(), {
language: null,
filename: null,
highlights: () => [],
});
const copy = () => {
navigator.clipboard.writeText(props.code);
};
</script>
<template>
<div :class="$style.wrapper">
<button :class="$style.language" @click="copy">
<span :class="$style.icon">
<UiIconCopy width="12" height="12" fill="currentColor" />
</span>
<span v-if="language">{{ language }}</span>
</button>
<slot />
</div>
</template>
<style>
pre code .line {
display: block;
min-height: 1rem;
}
</style>
<style lang="scss" module>
.wrapper {
position: relative;
}
.icon {
margin: 0 4px -2px 0;
}
.language {
cursor: pointer;
position: absolute;
right: 0;
top: 0;
padding: 4px 8px;
border-radius: 0 4px 0 4px;
text-transform: uppercase;
font-size: 0.75rem;
font-weight: 400;
background: var(--color-code-language-background);
color: var(--color-code-language-name);
user-select: none;
opacity: 0;
transition: all 250ms;
display: flex;
align-items: center;
justify-content: center;
.wrapper:hover & {
opacity: 0.7;
&:hover {
opacity: 1;
}
}
&:active {
transform: scale(1.1);
opacity: 1;
}
}
</style>

View file

@ -0,0 +1,34 @@
<template>
<masonry-wall
:items="parentItems"
:ssr-columns="1"
:column-width="300"
:gap="10"
>
<template #default="{ item }">
<div :class="$style.row">
<LayoutMainMenuRow
:title="item.title"
:url="item.url"
:children="item.children"
/>
</div>
</template>
</masonry-wall>
</template>
<script setup>
const { data: navigation } = await useAsyncData("navigation", () => {
return fetchContentNavigation();
});
const parentItems = navigation.value.filter(
(it) => it.children && Array.isArray(it.children) && it.children.length > 0
);
</script>
<style lang="scss" module>
.row {
margin-bottom: 10px;
}
</style>

View file

@ -0,0 +1,51 @@
<template>
<article>
<h1>{{ item?.title }}</h1>
<ul v-if="item?.children?.length" :class="$style.list">
<li v-for="child in item.children" :key="item._id">
<NuxtLink :to="child._path">{{ child.title }}</NuxtLink>
</li>
</ul>
</article>
</template>
<script lang="ts" setup>
import { NavItem } from "@nuxt/content/dist/runtime/types";
interface Props {
url: string;
}
const findDeep = (items: NavItem[], path: string[]) => {
const item = items.find((it) => it._path.endsWith(path[0]));
if (!item || (path.length > 1 && !item.children?.length)) {
return null;
}
return path.length === 1
? item
: findDeep(item.children, path.slice(1, path.length));
};
const props = defineProps<Props>();
const { data: navigation } = await useAsyncData("navigation", () => {
return fetchContentNavigation();
});
const segments = props.url.split("/").filter((it) => it);
const item = findDeep(navigation.value, segments);
</script>
<style lang="scss" module>
.list {
margin: 0;
padding: 0 20px;
li a {
text-decoration: none;
}
}
</style>

12
components/icons/Moon.vue Normal file
View file

@ -0,0 +1,12 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="48"
width="48"
viewBox="0 0 48 48"
>
<path
d="M17.85 7.55q-.7 0-1.45.075t-1.3.125q3.05 3.45 4.675 7.6Q21.4 19.5 21.4 24t-1.625 8.65Q18.15 36.8 15.15 40.2q.5.1 1.225.175.725.075 1.525.075 6.8 0 11.6-4.775T34.3 24q0-6.9-4.825-11.675T17.85 7.55Zm.25-1.5q3.6 0 6.85 1.375 3.25 1.375 5.65 3.8 2.4 2.425 3.8 5.7 1.4 3.275 1.4 7.025 0 3.75-1.425 7.05t-3.8 5.75Q28.2 39.2 24.95 40.575t-6.9 1.375q-1.65 0-3.125-.275t-2.675-.725q3.65-3.35 5.65-7.725 2-4.375 2-9.225 0-4.75-2-9.175-2-4.425-5.65-7.775 1.15-.45 2.675-.725Q16.45 6.05 18.1 6.05ZM21.4 24Z"
/>
</svg>
</template>

12
components/icons/Sun.vue Normal file
View file

@ -0,0 +1,12 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="48"
width="48"
viewBox="0 0 48 48"
>
<path
d="M24 30.45q2.65 0 4.55-1.875T30.45 24q0-2.65-1.875-4.55T24 17.55q-2.65 0-4.55 1.875T17.55 24q0 2.65 1.875 4.55T24 30.45ZM24 32q-3.35 0-5.675-2.325Q16 27.35 16 24q0-3.35 2.325-5.675Q20.65 16 24 16q3.35 0 5.675 2.325Q32 20.65 32 24q0 3.35-2.325 5.675Q27.35 32 24 32ZM3.75 24.75q-.3 0-.525-.225Q3 24.3 3 24q0-.35.225-.55.225-.2.525-.2h5.5q.3 0 .525.225Q10 23.7 10 24q0 .35-.225.55-.225.2-.525.2Zm35 0q-.3 0-.525-.225Q38 24.3 38 24q0-.35.225-.55.225-.2.525-.2h5.5q.3 0 .525.225Q45 23.7 45 24q0 .35-.225.55-.225.2-.525.2ZM24 10q-.35 0-.55-.225-.2-.225-.2-.525v-5.5q0-.3.225-.525Q23.7 3 24 3q.35 0 .55.225.2.225.2.525v5.5q0 .3-.225.525Q24.3 10 24 10Zm0 35q-.35 0-.55-.225-.2-.225-.2-.525v-5.5q0-.3.225-.525Q23.7 38 24 38q.35 0 .55.225.2.225.2.525v5.5q0 .3-.225.525Q24.3 45 24 45ZM13.05 14.05l-3.2-3.1q-.25-.2-.225-.525.025-.325.225-.575.25-.25.55-.25.3 0 .55.25L14.1 13q.25.25.25.55 0 .3-.25.55-.2.2-.5.2t-.55-.25Zm24 24.1L33.9 35q-.25-.25-.25-.55 0-.3.3-.55.15-.25.45-.225.3.025.55.275l3.2 3.1q.25.2.225.525-.025.325-.225.575-.25.25-.55.25-.3 0-.55-.25ZM33.9 14.1q-.25-.2-.225-.5.025-.3.275-.55l3.1-3.2q.2-.25.525-.225.325.025.575.225.25.25.25.55 0 .3-.25.55L35 14.1q-.25.25-.55.25-.3 0-.55-.25ZM9.85 38.15q-.25-.25-.25-.55 0-.3.25-.55L13 33.9q.25-.25.55-.25.3 0 .55.25.2.2.2.5t-.25.55l-3.1 3.2q-.25.25-.55.25-.3 0-.55-.25ZM24 24Z"
/>
</svg>
</template>

View file

@ -0,0 +1,26 @@
<template>
<footer :class="[$style.footer, $attrs.class]">
<div>btw, have a nice day</div>
<div :class="$style.filler" />
<div>
(2018 - {{ new Date().getFullYear() }})
<NuxtLink to="https://github.com/muerwre/" target="_blank"
>muerwre</NuxtLink
>
</div>
</footer>
</template>
<style lang="scss" module>
.footer {
color: var(--color-text-secondary);
font-size: 0.8rem;
display: flex;
flex-direction: row;
width: 100%;
}
.filler {
flex: 1;
}
</style>

View file

@ -0,0 +1,36 @@
<template>
<nav>
<div :class="$style.section_title">Reference</div>
<div v-for="item in parentItems" key="item._path" :class="$style.row">
<LayoutMainMenuRow
:title="item.title"
:url="item._path"
:children="item.children"
/>
</div>
</nav>
</template>
<script setup>
const { data: navigation } = await useAsyncData("navigation", () => {
return fetchContentNavigation();
});
const parentItems = navigation.value.filter(
(it) => it.children && Array.isArray(it.children) && it.children.length > 0
);
</script>
<style lang="scss" module>
.section_title {
font-family: var(--family-roboto-slab);
font-weight: 600;
margin: 2rem 0 1.5rem;
font-size: 1.6rem;
}
.row {
margin-bottom: 15px;
}
</style>

View file

@ -0,0 +1,158 @@
<template>
<div
v-if="children?.length || !url"
:class="[$style.container, { [$style.secondary]: secondary }]"
>
<div :class="$style.heading">
{{ title }}
</div>
<div :class="$style.children">
<LayoutMainMenuRow
v-for="item in children"
key="item._path"
:title="item.title"
:url="item._path"
:children="item.children"
secondary
/>
</div>
</div>
<div v-else :class="$style.row">
<NuxtLink :to="url" :class="$style.link" :exactActiveClass="$style.active"
>{{ title }}
</NuxtLink>
</div>
</template>
<script lang="ts" setup>
interface Props {
title: string;
url?: string;
children?: Child[];
secondary?: boolean;
}
interface Child {
title: string;
_path: string;
children: Child[];
}
defineProps<Props>();
</script>
<script lang="ts">
export default defineComponent({
mounted() {
const active = document.querySelector(
`.${this.$style.link}.${this.$style.active}`
);
if (!active) return;
active?.scrollIntoView({ block: "center" });
},
});
</script>
<style lang="scss" module>
@mixin tree {
&::before {
content: " ";
background-color: var(--color-menu-line);
width: 10px;
height: 1px;
position: absolute;
top: 0.6em;
left: -17px;
}
}
.container {
position: relative;
&.secondary {
padding: 7px 2px 0;
&::before {
content: " ";
background-color: var(--color-menu-line);
width: 1px;
position: absolute;
top: -22px;
bottom: 13px;
left: -16px;
}
&:first-child::before {
top: -4px;
}
&:last-child::before {
bottom: auto;
height: 40px;
}
}
}
.row {
padding: 3px 2px;
position: relative;
&::before {
content: " ";
background-color: var(--color-menu-line);
width: 1px;
position: absolute;
top: -14px;
bottom: 13px;
left: -16px;
}
&:first-child::before {
top: -4px;
}
&:last-child::before {
bottom: auto;
height: 30px;
}
&:only-child::before {
height: 19px;
}
}
.heading {
font-weight: 600;
display: flex;
align-items: center;
text-transform: uppercase;
position: relative;
color: var(--color-menu-title);
.secondary & {
@include tree;
}
}
.link {
color: var(--color-menu-link);
text-decoration: none;
line-height: 1.4em;
position: relative;
@include tree;
&.active {
color: var(--color-menu-link-active);
font-weight: bold;
}
}
.children {
padding: 0 0 0 16px;
margin: 10px 3px;
position: relative;
}
</style>

View file

@ -0,0 +1,67 @@
<template>
<button :class="[$attrs.class, $style.button]">
<svg
xmlns="http://www.w3.org/2000/svg"
height="24"
viewBox="0 0 24 24"
width="24"
fill="#ffffff"
:class="[$style.hamburger, { [$style.active]: active }]"
>
<rect x="0" y="3" width="24" height="2" />
<rect x="0" y="11" width="24" height="2" />
<rect x="0" y="19" width="24" height="2" />
</svg>
</button>
</template>
<script lang="ts" setup>
interface Props {
active?: boolean;
}
defineProps<Props>();
</script>
<style lang="scss" module>
.button {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
}
.hamburger {
fill: var(--color-text);
cursor: pointer;
transition: all 250ms;
&:hover {
fill: var(--color-link);
}
& > rect {
transition: transform 250ms;
}
&.active {
& > rect:nth-child(1) {
transform: rotate(45deg);
transform-origin: 2px 8px;
}
& > rect:nth-child(2) {
transform: scaleX(0);
transform-origin: 13px 0;
transition-delay: 100ms;
}
& > rect:nth-child(3) {
transform: rotate(-45deg);
transform-origin: 3px 16px;
transition-delay: 50ms;
}
}
}
</style>

View file

@ -0,0 +1,52 @@
<template>
<button
@click="toggleTheme"
:class="[$attrs.class, $style.button, { [$style.visible]: visible }]"
>
<ClientOnly>
<IconsMoon fill="currentColor" width="32" height="32" v-if="isDark" />
<IconsSun fill="currentColor" width="32" height="32" v-if="!isDark" />
</ClientOnly>
</button>
</template>
<script lang="ts" setup>
const visible = ref(false);
onMounted(() => {
visible.value = true;
});
</script>
<script lang="ts">
export default defineComponent({
methods: {
toggleTheme() {
this.$colorMode.preference =
this.$colorMode.preference === "dark" ? "light" : "dark";
},
},
computed: {
isDark() {
return this.$colorMode.preference === "dark";
},
},
});
</script>
<style lang="scss" module>
.button {
color: var(--color-text-secondary);
cursor: pointer;
transform: scale(0) rotate(180deg);
transition: all 0.25s ease-out;
&.visible {
transform: scale(1) rotate(0);
}
&:hover {
color: var(--color-text);
}
}
</style>

View file

@ -0,0 +1,14 @@
<template>
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 0 24 24"
width="24px"
fill="#ffffff"
>
<path d="M0 0h24v24H0z" fill="none" />
<path
d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"
/>
</svg>
</template>