mirror of
https://github.com/muerwre/muerwre.github.io.git
synced 2025-04-24 18:36:41 +07:00
initial
This commit is contained in:
commit
5104c2518b
34 changed files with 6844 additions and 0 deletions
11
.dockerignore
Normal file
11
.dockerignore
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
node_modules
|
||||||
|
*.log*
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
.output
|
||||||
|
.env
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
content/.obsidian
|
||||||
|
.DS_Store
|
28
.drone.docker.yml
Normal file
28
.drone.docker.yml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
kind: pipeline
|
||||||
|
name: build
|
||||||
|
type: docker
|
||||||
|
|
||||||
|
platform:
|
||||||
|
os: linux
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build-master
|
||||||
|
image: plugins/docker
|
||||||
|
when:
|
||||||
|
branch:
|
||||||
|
- master
|
||||||
|
settings:
|
||||||
|
dockerfile: docker/Dockerfile
|
||||||
|
tag:
|
||||||
|
- ${DRONE_BRANCH}
|
||||||
|
custom_labels:
|
||||||
|
- "commit=${DRONE_COMMIT_SHA}"
|
||||||
|
username:
|
||||||
|
from_secret: global_docker_login
|
||||||
|
password:
|
||||||
|
from_secret: global_docker_password
|
||||||
|
registry:
|
||||||
|
from_secret: global_docker_registry
|
||||||
|
repo:
|
||||||
|
from_secret: docker_repo
|
29
.drone.gh-pages.yml
Normal file
29
.drone.gh-pages.yml
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
kind: pipeline
|
||||||
|
name: build
|
||||||
|
type: docker
|
||||||
|
|
||||||
|
platform:
|
||||||
|
os: linux
|
||||||
|
arch: amd64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
|
image: node:16
|
||||||
|
commands:
|
||||||
|
- yarn
|
||||||
|
- yarn generate
|
||||||
|
- rm -rf ./docs
|
||||||
|
- mv ./.output/public ./docs
|
||||||
|
- touch ./docs/.nojekyll
|
||||||
|
- name: publish
|
||||||
|
image: plugins/gh-pages
|
||||||
|
settings:
|
||||||
|
target_branch: gh-pages
|
||||||
|
ssh_key:
|
||||||
|
from_secret: global_ssh_key
|
||||||
|
username:
|
||||||
|
from_secret: github_username
|
||||||
|
password:
|
||||||
|
from_secret: global_github_token
|
||||||
|
ssh_key:
|
||||||
|
from_secret: global_ssh_key
|
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
node_modules
|
||||||
|
*.log*
|
||||||
|
.nuxt
|
||||||
|
.nitro
|
||||||
|
.cache
|
||||||
|
.output
|
||||||
|
.env
|
||||||
|
dist
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
content/.obsidian
|
||||||
|
.DS_Store
|
||||||
|
.obsidian
|
0
.nojekyll
Normal file
0
.nojekyll
Normal file
35
README.md
Normal file
35
README.md
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# Self-hosted Obsidian Vault
|
||||||
|
|
||||||
|
Use [Obsidian](https://obsidian.md) for content editing at `./content` folder. Made with
|
||||||
|
[NuxtJS](https://v3.nuxtjs.org) and [Nuxt Content Plugin](https://content.nuxtjs.org/).
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn
|
||||||
|
yarn dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Publishing
|
||||||
|
|
||||||
|
```shell
|
||||||
|
yarn generate
|
||||||
|
cp -a ./.outputs/public ./somewhere
|
||||||
|
```
|
||||||
|
|
||||||
|
- Dockerfile included in `./docker`
|
||||||
|
- Sample `drone-ci` configurations for gh-pages (`./.drone.gh-pages.yml`) and docker registry
|
||||||
|
(`./.drone.docker.yml`).
|
||||||
|
|
||||||
|
## Supported Obsidian features
|
||||||
|
|
||||||
|
- WikiLinks (should be set up to relative in order to work)
|
||||||
|
- Highlight
|
||||||
|
- Code blocks
|
||||||
|
- Nested pages
|
||||||
|
|
||||||
|
## Other feature
|
||||||
|
|
||||||
|
- Adaptive layout
|
||||||
|
- SEO Optimized
|
||||||
|
- Day / Night theme switching
|
27
assets/css/_mixins.scss
Normal file
27
assets/css/_mixins.scss
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
@import "./variables";
|
||||||
|
|
||||||
|
@mixin phone {
|
||||||
|
@media (max-width: $size-phone) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin tablet {
|
||||||
|
@media (max-width: $size-tablet) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin desktop {
|
||||||
|
@media (max-width: $size-desktop) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin color-per-child($colors) {
|
||||||
|
@each $color in $colors {
|
||||||
|
&:nth-child(#{index(($colors), ($color))}) {
|
||||||
|
color: $color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
assets/css/_transitions.scss
Normal file
10
assets/css/_transitions.scss
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.page-enter-active,
|
||||||
|
.page-leave-active {
|
||||||
|
transition: all 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-enter-from,
|
||||||
|
.page-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(0, 50px);
|
||||||
|
}
|
3
assets/css/_variables.scss
Normal file
3
assets/css/_variables.scss
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
$size-phone: 560px;
|
||||||
|
$size-tablet: 768px;
|
||||||
|
$size-desktop: 1024px;
|
206
assets/css/main.scss
Normal file
206
assets/css/main.scss
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
@import url("https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@600&family=Roboto:wght@400;700&display=swap");
|
||||||
|
@import "./variables";
|
||||||
|
@import "./transitions.scss";
|
||||||
|
|
||||||
|
body,
|
||||||
|
html {
|
||||||
|
font-family: var(--family-roboto);
|
||||||
|
background: var(--color-background);
|
||||||
|
color: var(--color-text);
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
transition: color 250ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-link);
|
||||||
|
|
||||||
|
h1 > &,
|
||||||
|
h2 > &,
|
||||||
|
h3 > &,
|
||||||
|
h4 > &,
|
||||||
|
h5 > & {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--color-header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: var(--color-code-background);
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: scroll;
|
||||||
|
width: 100%;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
p > code {
|
||||||
|
background-color: var(--color-code-background);
|
||||||
|
color: var(--color-code-inline);
|
||||||
|
padding: 0 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5 {
|
||||||
|
font-family: var(--family-roboto-slab);
|
||||||
|
color: var(--color-header);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: var(--color-heading-primary);
|
||||||
|
font-size: 2.6rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: var(--color-heading-secondary);
|
||||||
|
|
||||||
|
&:not(:first-child) {
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5 {
|
||||||
|
color: var(--color-heading-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
li {
|
||||||
|
line-height: 1.45em;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
&:not(:last-child) {
|
||||||
|
margin-bottom: 0.25em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 2px solid var(--color-line);
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
border: 1px solid var(--color-line);
|
||||||
|
padding: 5px 10px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background: var(--color-table-head);
|
||||||
|
border-bottom: 2px solid var(--color-line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left: 3px solid var(--color-primary);
|
||||||
|
color: var(--color-text);
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
padding-left: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: var(--color-highlight-background);
|
||||||
|
color: var(--color-highlight-color);
|
||||||
|
padding: 0 1px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
// fonts
|
||||||
|
--family-roboto-slab: "Roboto Slab", "Segoe UI", Tahoma, Geneva, Verdana,
|
||||||
|
sans-serif;
|
||||||
|
--family-roboto: "Roboto", "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
|
||||||
|
// breakpoints
|
||||||
|
--size-phone: $phone;
|
||||||
|
--size-tablet: $size-tablet;
|
||||||
|
--size-desktop: $size-desktop;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.dark {
|
||||||
|
// palette
|
||||||
|
--color-primary: #e84a72;
|
||||||
|
--color-background: #16161c;
|
||||||
|
--color-menu-background: #1a1c23;
|
||||||
|
--color-line: #2d2f36;
|
||||||
|
--color-menu-overlay-background: #{transparentize(#16161c, 0.1)};
|
||||||
|
--color-code-background: #1a1c23;
|
||||||
|
--color-code-language-background: #{lighten(#1a1c23, 4%)};
|
||||||
|
--color-code-language-name: #1eaeae;
|
||||||
|
--color-text: #fdf0ed;
|
||||||
|
--color-text-secondary: #{mix(#ffffff, #1a1c23, 60%)};
|
||||||
|
--color-link: #e84a72;
|
||||||
|
--color-code-inline: #1eb980;
|
||||||
|
--color-heading-primary: white;
|
||||||
|
--color-heading-secondary: #f9cbbe;
|
||||||
|
--color-heading-tertiary: #f9cec3;
|
||||||
|
--color-menu-title: #fadad1;
|
||||||
|
--color-menu-link: #fab28e;
|
||||||
|
--color-menu-link-active: #e84a72;
|
||||||
|
--color-menu-line: #2e303e;
|
||||||
|
--color-table-head: #{mix(#e84a72, #1a1c23, 10%)};
|
||||||
|
--color-rating-1: #ded187;
|
||||||
|
--color-rating-2: #dbde87;
|
||||||
|
--color-rating-3: #bade87;
|
||||||
|
--color-rating-4: #9cde87;
|
||||||
|
--color-rating-5: #87deaa;
|
||||||
|
--color-highlight-color: var(--color-text);
|
||||||
|
--color-highlight-background: #254e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root.light {
|
||||||
|
$pinky: #{mix(#fadad1, #fce9e4, 50%)};
|
||||||
|
|
||||||
|
--color-primary: #e84a72;
|
||||||
|
--color-background: #fce9e4;
|
||||||
|
--color-menu-background: #{$pinky};
|
||||||
|
--color-line: #{$pinky};
|
||||||
|
--color-menu-overlay-background: #{transparentize(#16161c, 0.1)};
|
||||||
|
--color-code-background: #{$pinky};
|
||||||
|
--color-code-language-background: #{lighten(#1a1c23, 4%)};
|
||||||
|
--color-code-language-name: #1eaeae;
|
||||||
|
--color-text: #5a5d68;
|
||||||
|
--color-text-secondary: #{mix(#ffffff, #5a5d68, 20%)};
|
||||||
|
--color-link: #e84a72;
|
||||||
|
--color-code-inline: #8931b9;
|
||||||
|
--color-heading-primary: #4c5161;
|
||||||
|
--color-heading-secondary: #{mix(#f9cbbe, #1eaeae, 35%)};
|
||||||
|
--color-heading-tertiary: #{mix(#f9cbbe, #1eaeae, 35%)};
|
||||||
|
--color-menu-title: #{mix(#f9cbbe, #1eaeae, 35%)};
|
||||||
|
--color-menu-link: #{mix(#f9cbbe, #e84a72, 20%)};
|
||||||
|
--color-menu-link-active: #e84a72;
|
||||||
|
--color-menu-line: #f9cbbe;
|
||||||
|
--color-table-head: #{mix(#e84a72, #fadad1, 10%)};
|
||||||
|
--color-highlight-color: var(--color-text);
|
||||||
|
--color-highlight-background: #fab795;
|
||||||
|
}
|
35
components/content/ProseA.vue
Normal file
35
components/content/ProseA.vue
Normal 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>
|
81
components/content/ProseCode.vue
Normal file
81
components/content/ProseCode.vue
Normal 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>
|
34
components/home/HomeContentList.vue
Normal file
34
components/home/HomeContentList.vue
Normal 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>
|
51
components/home/HomeReference.vue
Normal file
51
components/home/HomeReference.vue
Normal 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
12
components/icons/Moon.vue
Normal 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
12
components/icons/Sun.vue
Normal 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>
|
26
components/layout/LayoutFooter.vue
Normal file
26
components/layout/LayoutFooter.vue
Normal 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>
|
36
components/layout/LayoutMainMenu.vue
Normal file
36
components/layout/LayoutMainMenu.vue
Normal 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>
|
158
components/layout/LayoutMainMenuRow.vue
Normal file
158
components/layout/LayoutMainMenuRow.vue
Normal 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>
|
67
components/layout/LayoutMainMenuToggle.vue
Normal file
67
components/layout/LayoutMainMenuToggle.vue
Normal 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>
|
52
components/layout/LayoutThemeToggle.vue
Normal file
52
components/layout/LayoutThemeToggle.vue
Normal 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>
|
14
components/ui/UiIconCopy.vue
Normal file
14
components/ui/UiIconCopy.vue
Normal 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>
|
7
custom.d.ts
vendored
Normal file
7
custom.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import { decl } from "postcss";
|
||||||
|
import { NitroAppPlugin, NitroApp } from "nitropack";
|
||||||
|
|
||||||
|
declare module "*.svg" {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
11
docker/Dockerfile
Normal file
11
docker/Dockerfile
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
FROM node:16-alpine as builder
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json yarn.lock ./
|
||||||
|
RUN yarn
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN yarn generate
|
||||||
|
|
||||||
|
FROM nginx
|
||||||
|
COPY docker/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY --from=builder /app/dist /usr/share/nginx/html
|
54
docker/nginx.conf
Normal file
54
docker/nginx.conf
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
worker_processes 4;
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
|
||||||
|
gzip on;
|
||||||
|
gzip_min_length 1000;
|
||||||
|
gzip_proxied expired no-cache no-store private auth;
|
||||||
|
gzip_types text/plain application/xml application/javascript;
|
||||||
|
|
||||||
|
## All static files will be served directly.
|
||||||
|
location ~* ^.+\.(?:css|cur|js|jpe?g|gif|htc|ico|png|xml|otf|ttf|eot|woff|woff2|svg)$ {
|
||||||
|
access_log off;
|
||||||
|
expires 1d;
|
||||||
|
add_header Cache-Control public;
|
||||||
|
gzip_static on;
|
||||||
|
|
||||||
|
## No need to bleed constant updates. Send the all shebang in one
|
||||||
|
## fell swoop.
|
||||||
|
tcp_nodelay off;
|
||||||
|
|
||||||
|
## Set the OS file cache.
|
||||||
|
open_file_cache max=3000 inactive=120s;
|
||||||
|
open_file_cache_valid 45s;
|
||||||
|
open_file_cache_min_uses 2;
|
||||||
|
open_file_cache_errors off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# try_files $uri @index;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
add_header Last-Modified $date_gmt;
|
||||||
|
add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0';
|
||||||
|
if_modified_since off;
|
||||||
|
expires off;
|
||||||
|
etag off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location @index {
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||||
|
expires -1;
|
||||||
|
try_files /index.html =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
54
layouts/content.vue
Normal file
54
layouts/content.vue
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
<template>
|
||||||
|
<div :class="$style.wrapper">
|
||||||
|
<div :class="$style.content">
|
||||||
|
<LayoutThemeToggle :class="$style.theme_toggle" />
|
||||||
|
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<LayoutFooter :class="$style.footer" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" module>
|
||||||
|
@import "~~/assets/css/mixins";
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
padding: 40px 120px;
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
padding: 40px 40px 20px 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include tablet {
|
||||||
|
padding: 40px 20px 20px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 60px;
|
||||||
|
padding: 10px;
|
||||||
|
border-top: 1px solid var(--color-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme_toggle {
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-width: 0.5px;
|
||||||
|
|
||||||
|
@include tablet {
|
||||||
|
stroke-width: 1.5px;
|
||||||
|
right: 16px;
|
||||||
|
top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
165
layouts/default.vue
Normal file
165
layouts/default.vue
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
<template>
|
||||||
|
<div :class="$style.grid">
|
||||||
|
<LayoutMainMenuToggle
|
||||||
|
:active="menuVisible"
|
||||||
|
:class="[
|
||||||
|
$style.menu_toggle,
|
||||||
|
{ [$style.shifted]: menuShifted, [$style.active]: menuVisible },
|
||||||
|
]"
|
||||||
|
@click="toggleMenu"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div
|
||||||
|
:class="[$style.sidebar, { [$style.active]: menuVisible }]"
|
||||||
|
ref="sidebar"
|
||||||
|
>
|
||||||
|
<div :class="$style.menu">
|
||||||
|
<LayoutMainMenu />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="$style.main">
|
||||||
|
<NuxtLayout name="content">
|
||||||
|
<slot />
|
||||||
|
</NuxtLayout>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { disableBodyScroll, clearAllBodyScrollLocks } from "body-scroll-lock";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
setup() {
|
||||||
|
const scrollTop = ref(0);
|
||||||
|
const onScroll = () => {
|
||||||
|
scrollTop.value = window.scrollY;
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => addEventListener("scroll", onScroll));
|
||||||
|
onUnmounted(() => removeEventListener("scroll", onScroll));
|
||||||
|
|
||||||
|
const menuShifted = computed(() => scrollTop.value > 60);
|
||||||
|
|
||||||
|
return { menuShifted, scrollTop };
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
menuVisible: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleMenu() {
|
||||||
|
this.$data.menuVisible = !this.$data.menuVisible;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route() {
|
||||||
|
if (!this.menuVisible) return;
|
||||||
|
nextTick(() => this.toggleMenu());
|
||||||
|
},
|
||||||
|
menuVisible(val) {
|
||||||
|
if (val) {
|
||||||
|
disableBodyScroll(this.$refs.sidebar);
|
||||||
|
}
|
||||||
|
|
||||||
|
clearAllBodyScrollLocks();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
@import "~~/assets/css/mixins";
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 360px auto;
|
||||||
|
width: 100vw;
|
||||||
|
|
||||||
|
@include desktop {
|
||||||
|
grid-template-columns: 33vw auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include tablet {
|
||||||
|
grid-template-columns: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
min-width: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
background-color: var(--color-menu-background);
|
||||||
|
|
||||||
|
@include tablet {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 2;
|
||||||
|
background-color: var(--color-menu-overlay-background);
|
||||||
|
width: 100%;
|
||||||
|
visibility: hidden;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.5s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
padding: 40px 30px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
overflow: auto;
|
||||||
|
min-width: 0;
|
||||||
|
background-color: var(--color-menu-background);
|
||||||
|
max-width: 400px;
|
||||||
|
|
||||||
|
@include tablet {
|
||||||
|
transition: transform 0.25s 0.1s;
|
||||||
|
transform: translate(-40px, 0);
|
||||||
|
|
||||||
|
.active & {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu_toggle {
|
||||||
|
position: fixed;
|
||||||
|
left: 13px;
|
||||||
|
top: 13px;
|
||||||
|
z-index: 4;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translate(0, 0);
|
||||||
|
transition: all 250ms;
|
||||||
|
border-radius: 0 0 8px 0;
|
||||||
|
|
||||||
|
@include tablet {
|
||||||
|
transform: translate(0, 0);
|
||||||
|
right: 0;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.shifted,
|
||||||
|
&.active {
|
||||||
|
transform: translate(-13px, -13px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.shifted {
|
||||||
|
background: var(--color-menu-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
</style>
|
50
nuxt.config.ts
Normal file
50
nuxt.config.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
export default defineNuxtConfig({
|
||||||
|
alias: {
|
||||||
|
"~": "/<rootDir>",
|
||||||
|
},
|
||||||
|
modules: ["@nuxt/content", "@nuxtjs/color-mode"],
|
||||||
|
typescript: {
|
||||||
|
shim: false,
|
||||||
|
typeCheck: true,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
navigation: {
|
||||||
|
fields: ["blblblb"],
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
theme: {
|
||||||
|
default: "github-dark",
|
||||||
|
light: "solarized-light",
|
||||||
|
},
|
||||||
|
preload: [
|
||||||
|
"shell",
|
||||||
|
"c",
|
||||||
|
"go",
|
||||||
|
"graphql",
|
||||||
|
"scss",
|
||||||
|
"shell",
|
||||||
|
"sh",
|
||||||
|
"docker",
|
||||||
|
"typescript",
|
||||||
|
"javascript",
|
||||||
|
"nginx",
|
||||||
|
"bash",
|
||||||
|
"yaml",
|
||||||
|
"sh",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
target: "static",
|
||||||
|
css: ["@/assets/css/main.scss"],
|
||||||
|
head: {
|
||||||
|
link: [{ rel: "icon", type: "image/png", href: "/favicon.png" }],
|
||||||
|
},
|
||||||
|
colorMode: {
|
||||||
|
preference: "dark",
|
||||||
|
classSuffix: "",
|
||||||
|
storageKey: "nuxt-color-mode",
|
||||||
|
},
|
||||||
|
app: {
|
||||||
|
buildAssetsDir: "nuxt/",
|
||||||
|
},
|
||||||
|
});
|
28
package.json
Normal file
28
package.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"typings": "*.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare",
|
||||||
|
"tsc": "nuxi typecheck"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@lewebsimple/nuxt3-svg": "^0.1.1",
|
||||||
|
"@nuxt/content": "^2.2.0",
|
||||||
|
"@nuxtjs/color-mode": "^3.1.8",
|
||||||
|
"add": "^2.0.6",
|
||||||
|
"nuxt": "3.0.0-rc.12",
|
||||||
|
"typescript": "^4.8.4",
|
||||||
|
"vue-tsc": "^1.0.9",
|
||||||
|
"yarn": "^1.22.19"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@yeger/vue-masonry-wall": "^3.2.14",
|
||||||
|
"body-scroll-lock": "^4.0.0-beta.0",
|
||||||
|
"sass": "^1.55.0",
|
||||||
|
"vue-masonry-css": "^1.0.3"
|
||||||
|
}
|
||||||
|
}
|
30
pages/[...slug].vue
Normal file
30
pages/[...slug].vue
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<main>
|
||||||
|
<ContentDoc>
|
||||||
|
<template v-slot="{ doc }">
|
||||||
|
<h1>{{ doc.title }}</h1>
|
||||||
|
<article>
|
||||||
|
<ContentRenderer :value="doc" />
|
||||||
|
</article>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:not-found="{ props: { path } }">
|
||||||
|
<HomeReference :url="path" />
|
||||||
|
</template>
|
||||||
|
</ContentDoc>
|
||||||
|
</main>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
useHead({
|
||||||
|
titleTemplate: (titleChunk) => {
|
||||||
|
return titleChunk ? `${titleChunk} • Obsidian Garden` : "Obsidian Garden";
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export default {
|
||||||
|
scrollToTop: true,
|
||||||
|
};
|
||||||
|
</script>
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
10
server/plugins/highlight.ts
Normal file
10
server/plugins/highlight.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
export default defineNitroPlugin((nitroApp) => {
|
||||||
|
nitroApp.hooks.hook("content:file:beforeParse", (file) => {
|
||||||
|
if (file._id.endsWith(".md")) {
|
||||||
|
file.body = file.body.replace(
|
||||||
|
/==([^=]+)==/gs,
|
||||||
|
`<span class="highlight">$1</span>`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
4
tsconfig.json
Normal file
4
tsconfig.json
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
// https://v3.nuxtjs.org/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json"
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue