Compare commits
158 Commits
Author | SHA1 | Date | |
---|---|---|---|
a7b7a5c797 | |||
ea0d791657 | |||
851e05a571 | |||
2f25a1ba0a | |||
b460b61375 | |||
|
0d1f15d37c | ||
|
11c23a137a | ||
|
8a95c865c8 | ||
|
fe4b039b60 | ||
|
497d51973a | ||
|
66d7dd8677 | ||
|
313cef60ee | ||
|
af14ca7c4f | ||
|
e06f681ce7 | ||
|
62906eebd3 | ||
|
9cd072bfc3 | ||
|
319b4497bc | ||
|
b0c079f24a | ||
|
1b122a1da0 | ||
|
b8c46ba81a | ||
|
1416f62a47 | ||
|
8889ab63eb | ||
|
5eec1e98e6 | ||
|
a7a0dcad22 | ||
|
921f45cf70 | ||
|
90c187587f | ||
|
08e20a7006 | ||
|
9e32016508 | ||
|
8a050c0be0 | ||
|
0aacd8ed2e | ||
|
7d7e334976 | ||
|
9cefcd0dd1 | ||
|
4aaeb768d8 | ||
d7b034e20b | |||
ca678d0237 | |||
bf31677330 | |||
e03c526ddb | |||
|
743ef712d5 | ||
|
9a6e4e2f80 | ||
|
14cb50de9b | ||
|
dad0ae4e3f | ||
|
9c060f3cf2 | ||
|
16a9caa555 | ||
|
b1c60b8833 | ||
|
50a78bafa5 | ||
|
6ea359e55e | ||
|
cd3bb25626 | ||
|
eb9bbd1666 | ||
|
c36551310d | ||
|
6215dd5565 | ||
|
c89c76b40a | ||
|
a145e320d0 | ||
|
082cbb74c3 | ||
|
93b2481261 | ||
|
d2414b3903 | ||
e6795bdea4 | |||
fd1cadaa65 | |||
91c1afeaf0 | |||
e3f2929bf0 | |||
942c5f16f1 | |||
7154af060d | |||
545e39fdd2 | |||
3056b74cb9 | |||
b5ec2662fc | |||
29b60d6264 | |||
1bc0820daf | |||
f02f6c01b7 | |||
cba2e5a1c6 | |||
a574b10e55 | |||
51f31934d1 | |||
f5c1ce115f | |||
7438fca363 | |||
04b9cff4fe | |||
441148fe84 | |||
7e8995ef2e | |||
0f02b9ba3d | |||
22f5e4d213 | |||
22f1b7a476 | |||
296787d242 | |||
b4654a24e2 | |||
74ae3057f3 | |||
738dd0024a | |||
26e0e5dd22 | |||
e3c4262834 | |||
145acf4128 | |||
c2b3b77dcc | |||
6f04db5d09 | |||
038aad9015 | |||
a7fc2b4ab2 | |||
a1505797e7 | |||
0d35db3843 | |||
98679a6aac | |||
07923196fb | |||
2e1d270837 | |||
130acb922e | |||
81a5ebc419 | |||
61c6720285 | |||
3256f3eba1 | |||
a8c42da44a | |||
320ab1896f | |||
8df0eb6473 | |||
55ce3dd934 | |||
2e47bf6d04 | |||
b9e4c6f74e | |||
be47c4a10a | |||
91313ee4b1 | |||
bd0273e94d | |||
3a4307e24d | |||
691755ac0b | |||
63e82e5765 | |||
5899506986 | |||
4fbaa69a5a | |||
41109cec4f | |||
17ade1f8bd | |||
5742c813e8 | |||
0eefe283d9 | |||
9d8ce1fd58 | |||
9533c27baf | |||
7f3f5bb174 | |||
99ad1dfc9f | |||
58ac6e8153 | |||
6c56d46980 | |||
eea2bb1465 | |||
f0d099da8d | |||
656af4a884 | |||
5ec339cc4c | |||
08d94b6ce3 | |||
c7607067db | |||
2fa8800f15 | |||
230e6b4175 | |||
ae7f306356 | |||
264667683b | |||
6f5b5b9a18 | |||
6cbe8bffbc | |||
01256a5ae1 | |||
764c15cd90 | |||
79a33c8271 | |||
3e70f7c79a | |||
b3afab86fd | |||
b6cde7ac96 | |||
c299e1e3e3 | |||
92ac595aa6 | |||
743d34fe5d | |||
133a34259b | |||
269b89e7de | |||
db4b14d53c | |||
db3eba231f | |||
11cd9295b2 | |||
becfc27348 | |||
02f1f6e56a | |||
650596f88b | |||
d82b9d059b | |||
3c37ee024b | |||
b748adc8a9 | |||
f56ed6f78b | |||
5dccc2e4cf | |||
e9ebe3dfe1 | |||
44acdbb47e |
21
.dockerignore
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
.git
|
||||||
|
.github
|
||||||
|
.drone.yml
|
||||||
|
.gitattributes
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.md
|
||||||
|
docs
|
||||||
|
.gitignore
|
||||||
|
LICENSE.txt
|
||||||
|
CODE_OF_CONDUCT.md
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
||||||
|
quartz.config.ts
|
||||||
|
quartz.layout.ts
|
||||||
|
content
|
100
.drone.yml
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: docker-build-and-push
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
branch:
|
||||||
|
- drone-v4
|
||||||
|
|
||||||
|
image_pull_secrets:
|
||||||
|
- DOCKER_AUTH
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: docker
|
||||||
|
# https://hub.docker.com/r/library/docker
|
||||||
|
image: hub.docker.struchkov.dev/docker:27.3.1-dind-alpine3.20
|
||||||
|
privileged: true
|
||||||
|
volumes:
|
||||||
|
- name: dockersock
|
||||||
|
path: /var/run
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: dockersock
|
||||||
|
temp: {}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: docker build an publish
|
||||||
|
# https://hub.docker.com/r/library/docker
|
||||||
|
image: docker.struchkov.dev/docker-buildx:latest
|
||||||
|
environment:
|
||||||
|
DOCKER_REGISTRY_TOKEN:
|
||||||
|
from_secret: DOCKER_REGISTRY_TOKEN
|
||||||
|
DOCKER_REGISTRY_USER:
|
||||||
|
from_secret: DOCKER_REGISTRY_USER
|
||||||
|
volumes:
|
||||||
|
- name: dockersock
|
||||||
|
path: /var/run
|
||||||
|
commands:
|
||||||
|
- sleep 15
|
||||||
|
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
|
||||||
|
- echo "$DOCKER_REGISTRY_TOKEN" | docker login hub.docker.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
|
||||||
|
- docker build -t "docker.struchkov.dev/quartz:develop" .
|
||||||
|
- docker push docker.struchkov.dev/quartz:develop
|
||||||
|
# - docker buildx create --use
|
||||||
|
# - docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 -t "docker.struchkov.dev/quartz:develop" .
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: docker-build-and-push-release
|
||||||
|
|
||||||
|
trigger:
|
||||||
|
ref:
|
||||||
|
- refs/tags/v*
|
||||||
|
|
||||||
|
image_pull_secrets:
|
||||||
|
- DOCKER_AUTH
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: node_cache_quartz
|
||||||
|
host:
|
||||||
|
path: /drone/volume/node_modules/quartz_release
|
||||||
|
- name: dockersock
|
||||||
|
temp: {}
|
||||||
|
|
||||||
|
services:
|
||||||
|
- name: docker
|
||||||
|
# https://hub.docker.com/r/library/docker
|
||||||
|
image: hub.docker.struchkov.dev/docker:27.3.1-dind-alpine3.20
|
||||||
|
privileged: true
|
||||||
|
volumes:
|
||||||
|
- name: dockersock
|
||||||
|
path: /var/run
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: docker build an publish
|
||||||
|
image: docker.struchkov.dev/docker-buildx:latest
|
||||||
|
environment:
|
||||||
|
DOCKER_REGISTRY_TOKEN:
|
||||||
|
from_secret: DOCKER_REGISTRY_TOKEN
|
||||||
|
DOCKER_REGISTRY_USER:
|
||||||
|
from_secret: DOCKER_REGISTRY_USER
|
||||||
|
volumes:
|
||||||
|
- name: node_cache_quartz
|
||||||
|
path: ${DRONE_WORKSPACE}/node_modules
|
||||||
|
- name: dockersock
|
||||||
|
path: /var/run
|
||||||
|
commands:
|
||||||
|
- sleep 15
|
||||||
|
- echo "$DOCKER_REGISTRY_TOKEN" | docker login docker.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
|
||||||
|
- echo "$DOCKER_REGISTRY_TOKEN" | docker login hub.docker.struchkov.dev --username $DOCKER_REGISTRY_USER --password-stdin
|
||||||
|
- docker buildx create --use
|
||||||
|
- docker buildx build --push --platform linux/arm/v7,linux/arm64/v8,linux/amd64 -t "docker.struchkov.dev/quartz:latest" -t "docker.struchkov.dev/quartz:$DRONE_TAG" .
|
||||||
|
|
||||||
|
|
||||||
|
# drone sign --save DockerFiles/quartz
|
||||||
|
---
|
||||||
|
kind: signature
|
||||||
|
hmac: e9c189beaff93f08b6126416d28bbf1bef7675454c8fa5338fcbb28397c0c538
|
||||||
|
|
||||||
|
...
|
10
.github/dependabot.yml
vendored
@ -1,11 +1,11 @@
|
|||||||
# To get started with Dependabot version updates, you'll need to specify which
|
|
||||||
# package ecosystems to update and where the package manifests are located.
|
|
||||||
# Please see the documentation for all configuration options:
|
|
||||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: "npm"
|
- package-ecosystem: "npm"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "weekly"
|
interval: "weekly"
|
||||||
|
groups:
|
||||||
|
production-dependencies:
|
||||||
|
applies-to: "version-updates"
|
||||||
|
patterns:
|
||||||
|
- "*"
|
||||||
|
88
.github/workflows/docker-build-push.yaml
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
name: Docker build & push image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [v4]
|
||||||
|
tags: ["v*"]
|
||||||
|
pull_request:
|
||||||
|
branches: [v4]
|
||||||
|
paths:
|
||||||
|
- .github/workflows/docker-build-push.yaml
|
||||||
|
- quartz/**
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
if: ${{ github.repository == 'jackyzha0/quartz' }} # Comment this out if you want to publish your own images on a fork!
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set lowercase repository owner environment variable
|
||||||
|
run: |
|
||||||
|
echo "OWNER_LOWERCASE=${OWNER,,}" >> ${GITHUB_ENV}
|
||||||
|
env:
|
||||||
|
OWNER: "${{ github.repository_owner }}"
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 1
|
||||||
|
- name: Inject slug/short variables
|
||||||
|
uses: rlespinasse/github-slug-action@v4.4.1
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
install: true
|
||||||
|
driver-opts: |
|
||||||
|
image=moby/buildkit:master
|
||||||
|
network=host
|
||||||
|
- name: Install cosign
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: sigstore/cosign-installer@v3.7.0
|
||||||
|
- name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Extract metadata tags and labels on PRs
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
id: meta-pr
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ env.OWNER_LOWERCASE }}/quartz
|
||||||
|
tags: |
|
||||||
|
type=raw,value=sha-${{ env.GITHUB_SHA_SHORT }}
|
||||||
|
labels: |
|
||||||
|
org.opencontainers.image.source="https://github.com/${{ github.repository_owner }}/quartz"
|
||||||
|
- name: Extract metadata tags and labels for main, release or tag
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
flavor: |
|
||||||
|
latest=auto
|
||||||
|
images: ghcr.io/${{ env.OWNER_LOWERCASE }}/quartz
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}.{{patch}}
|
||||||
|
type=raw,value=latest,enable=${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
|
||||||
|
type=raw,value=sha-${{ env.GITHUB_SHA_SHORT }}
|
||||||
|
labels: |
|
||||||
|
maintainer=${{ github.repository_owner }}
|
||||||
|
org.opencontainers.image.source="https://github.com/${{ github.repository_owner }}/quartz"
|
||||||
|
|
||||||
|
- name: Build and push Docker image
|
||||||
|
id: build-and-push
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
|
build-args: |
|
||||||
|
GIT_SHA=${{ env.GITHUB_SHA }}
|
||||||
|
DOCKER_LABEL=sha-${{ env.GITHUB_SHA_SHORT }}
|
||||||
|
tags: ${{ steps.meta.outputs.tags || steps.meta-pr.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels || steps.meta-pr.outputs.labels }}
|
||||||
|
cache-from: type=gha
|
||||||
|
cache-to: type=gha
|
19
Dockerfile
@ -1,11 +1,24 @@
|
|||||||
FROM node:20-slim as builder
|
FROM node:20-slim AS builder
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
COPY package.json .
|
COPY package.json .
|
||||||
COPY package-lock.json* .
|
COPY package-lock.json* .
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
FROM node:20-slim
|
FROM node:20-alpine
|
||||||
|
RUN apk update && apk add --no-cache \
|
||||||
|
optipng \
|
||||||
|
advancecomp \
|
||||||
|
pngcrush \
|
||||||
|
jpegoptim \
|
||||||
|
libwebp-tools \
|
||||||
|
findutils \
|
||||||
|
bash \
|
||||||
|
git \
|
||||||
|
openssh-client \
|
||||||
|
coreutils
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
COPY --from=builder /usr/src/app/ /usr/src/app/
|
COPY --from=builder /usr/src/app/ /usr/src/app/
|
||||||
COPY . .
|
COPY . .
|
||||||
CMD ["npx", "quartz", "build", "--serve"]
|
#RUN chmod +x zip_image.sh
|
||||||
|
ENTRYPOINT ["npx", "quartz"]
|
@ -21,3 +21,7 @@ This will start a local web server to run your Quartz on your computer. Open a w
|
|||||||
> - `--serve`: run a local hot-reloading server to preview your Quartz
|
> - `--serve`: run a local hot-reloading server to preview your Quartz
|
||||||
> - `--port`: what port to run the local preview server on
|
> - `--port`: what port to run the local preview server on
|
||||||
> - `--concurrency`: how many threads to use to parse notes
|
> - `--concurrency`: how many threads to use to parse notes
|
||||||
|
|
||||||
|
> [!warning] Not to be used for production
|
||||||
|
> Serve mode is intended for local previews only.
|
||||||
|
> For production workloads, see the page on [[hosting]].
|
||||||
|
@ -21,6 +21,7 @@ const config: QuartzConfig = {
|
|||||||
This part of the configuration concerns anything that can affect the whole site. The following is a list breaking down all the things you can configure:
|
This part of the configuration concerns anything that can affect the whole site. The following is a list breaking down all the things you can configure:
|
||||||
|
|
||||||
- `pageTitle`: title of the site. This is also used when generating the [[RSS Feed]] for your site.
|
- `pageTitle`: title of the site. This is also used when generating the [[RSS Feed]] for your site.
|
||||||
|
- `pageTitleSuffix`: a string added to the end of the page title. This only applies to the browser tab title, not the title shown at the top of the page.
|
||||||
- `enableSPA`: whether to enable [[SPA Routing]] on your site.
|
- `enableSPA`: whether to enable [[SPA Routing]] on your site.
|
||||||
- `enablePopovers`: whether to enable [[popover previews]] on your site.
|
- `enablePopovers`: whether to enable [[popover previews]] on your site.
|
||||||
- `analytics`: what to use for analytics on your site. Values can be
|
- `analytics`: what to use for analytics on your site. Values can be
|
||||||
@ -32,6 +33,7 @@ This part of the configuration concerns anything that can affect the whole site.
|
|||||||
- `{ provider: 'posthog', apiKey: '<your-posthog-project-apiKey>', host: '<your-posthog-host>' }`: use [Posthog](https://posthog.com/);
|
- `{ provider: 'posthog', apiKey: '<your-posthog-project-apiKey>', host: '<your-posthog-host>' }`: use [Posthog](https://posthog.com/);
|
||||||
- `{ provider: 'tinylytics', siteId: '<your-site-id>' }`: use [Tinylytics](https://tinylytics.app/);
|
- `{ provider: 'tinylytics', siteId: '<your-site-id>' }`: use [Tinylytics](https://tinylytics.app/);
|
||||||
- `{ provider: 'cabin' }` or `{ provider: 'cabin', host: 'https://cabin.example.com' }` (custom domain): use [Cabin](https://withcabin.com);
|
- `{ provider: 'cabin' }` or `{ provider: 'cabin', host: 'https://cabin.example.com' }` (custom domain): use [Cabin](https://withcabin.com);
|
||||||
|
- `{provider: 'clarity', projectId: '<your-clarity-id-code' }`: use [Microsoft clarity](https://clarity.microsoft.com/). The project id can be found on top of the overview page.
|
||||||
- `locale`: used for [[i18n]] and date formatting
|
- `locale`: used for [[i18n]] and date formatting
|
||||||
- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes.
|
- `baseUrl`: this is used for sitemaps and RSS feeds that require an absolute URL to know where the canonical 'home' of your site lives. This is normally the deployed URL of your site (e.g. `quartz.jzhao.xyz` for this site). Do not include the protocol (i.e. `https://`) or any leading or trailing slashes.
|
||||||
- This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`.
|
- This should also include the subpath if you are [[hosting]] on GitHub pages without a custom domain. For example, if my repository is `jackyzha0/quartz`, GitHub pages would deploy to `https://jackyzha0.github.io/quartz` and the `baseUrl` would be `jackyzha0.github.io/quartz`.
|
||||||
@ -101,7 +103,7 @@ transformers: [
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
Some plugins are included by default in the[ `quartz.config.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz.config.ts), but there are more available.
|
Some plugins are included by default in the [`quartz.config.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz.config.ts), but there are more available.
|
||||||
|
|
||||||
You can see a list of all plugins and their configuration options [[tags/plugin|here]].
|
You can see a list of all plugins and their configuration options [[tags/plugin|here]].
|
||||||
|
|
||||||
|
28
docs/features/Roam Research compatibility.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
title: "Roam Research Compatibility"
|
||||||
|
tags:
|
||||||
|
- feature/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
[Roam Research](https://roamresearch.com) is a note-taking tool that organizes your knowledge graph in a unique and interconnected way.
|
||||||
|
|
||||||
|
Quartz supports transforming the special Markdown syntax from Roam Research (like `{{[[components]]}}` and other formatting) into
|
||||||
|
regular Markdown via the [[RoamFlavoredMarkdown]] plugin.
|
||||||
|
|
||||||
|
```typescript title="quartz.config.ts"
|
||||||
|
plugins: {
|
||||||
|
transformers: [
|
||||||
|
// ...
|
||||||
|
Plugin.RoamFlavoredMarkdown(),
|
||||||
|
Plugin.ObsidianFlavoredMarkdown(),
|
||||||
|
// ...
|
||||||
|
],
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!warning]
|
||||||
|
> As seen above placement of `Plugin.RoamFlavoredMarkdown()` within `quartz.config.ts` is very important. It must come before `Plugin.ObsidianFlavoredMarkdown()`.
|
||||||
|
|
||||||
|
## Customization
|
||||||
|
|
||||||
|
This functionality is provided by the [[RoamFlavoredMarkdown]] plugin. See the plugin page for customization options.
|
Before Width: | Height: | Size: 65 KiB |
BIN
docs/images/quartz-layout-desktop.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
docs/images/quartz-layout-mobile.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
docs/images/quartz-layout-tablet.png
Normal file
After Width: | Height: | Size: 35 KiB |
@ -13,15 +13,19 @@ export interface FullPageLayout {
|
|||||||
beforeBody: QuartzComponent[] // laid out vertically
|
beforeBody: QuartzComponent[] // laid out vertically
|
||||||
pageBody: QuartzComponent // single component
|
pageBody: QuartzComponent // single component
|
||||||
afterBody: QuartzComponent[] // laid out vertically
|
afterBody: QuartzComponent[] // laid out vertically
|
||||||
left: QuartzComponent[] // vertical on desktop, horizontal on mobile
|
left: QuartzComponent[] // vertical on desktop and tablet, horizontal on mobile
|
||||||
right: QuartzComponent[] // vertical on desktop, horizontal on mobile
|
right: QuartzComponent[] // vertical on desktop, horizontal on tablet and mobile
|
||||||
footer: QuartzComponent // single component
|
footer: QuartzComponent // single component
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
These correspond to following parts of the page:
|
These correspond to following parts of the page:
|
||||||
|
|
||||||
![[quartz layout.png|800]]
|
| Layout | Preview |
|
||||||
|
| ------------------------------- | ----------------------------------- |
|
||||||
|
| Desktop (width > 1200px) | ![[quartz-layout-desktop.png\|800]] |
|
||||||
|
| Tablet (800px < width < 1200px) | ![[quartz-layout-tablet.png\|800]] |
|
||||||
|
| Mobile (width < 800px) | ![[quartz-layout-mobile.png\|800]] |
|
||||||
|
|
||||||
> [!note]
|
> [!note]
|
||||||
> There are two additional layout fields that are _not_ shown in the above diagram.
|
> There are two additional layout fields that are _not_ shown in the above diagram.
|
||||||
@ -33,6 +37,23 @@ Quartz **components**, like plugins, can take in additional properties as config
|
|||||||
|
|
||||||
See [a list of all the components](component.md) for all available components along with their configuration options. You can also checkout the guide on [[creating components]] if you're interested in further customizing the behaviour of Quartz.
|
See [a list of all the components](component.md) for all available components along with their configuration options. You can also checkout the guide on [[creating components]] if you're interested in further customizing the behaviour of Quartz.
|
||||||
|
|
||||||
|
### Layout breakpoints
|
||||||
|
|
||||||
|
Quartz has different layouts depending on the width the screen viewing the website.
|
||||||
|
|
||||||
|
The breakpoints for layouts can be configured in `variables.scss`.
|
||||||
|
|
||||||
|
- `mobile`: screen width below this size will use mobile layout.
|
||||||
|
- `desktop`: screen width above this size will use desktop layout.
|
||||||
|
- Screen width between `mobile` and `desktop` width will use the tablet layout.
|
||||||
|
|
||||||
|
```scss
|
||||||
|
$breakpoints: (
|
||||||
|
mobile: 800px,
|
||||||
|
desktop: 1200px,
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
### Style
|
### Style
|
||||||
|
|
||||||
Most meaningful style changes like colour scheme and font can be done simply through the [[configuration#General Configuration|general configuration]] options. However, if you'd like to make more involved style changes, you can do this by writing your own styles. Quartz 4, like Quartz 3, uses [Sass](https://sass-lang.com/guide/) for styling.
|
Most meaningful style changes like colour scheme and font can be done simply through the [[configuration#General Configuration|general configuration]] options. However, if you'd like to make more involved style changes, you can do this by writing your own styles. Quartz 4, like Quartz 3, uses [Sass](https://sass-lang.com/guide/) for styling.
|
||||||
|
26
docs/plugins/RoamFlavoredMarkdown.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
title: RoamFlavoredMarkdown
|
||||||
|
tags:
|
||||||
|
- plugin/transformer
|
||||||
|
---
|
||||||
|
|
||||||
|
This plugin provides support for [Roam Research](https://roamresearch.com) compatibility. See [[Roam Research Compatibility]] for more information.
|
||||||
|
|
||||||
|
> [!note]
|
||||||
|
> For information on how to add, remove or configure plugins, see the [[Configuration#Plugins|Configuration]] page.
|
||||||
|
|
||||||
|
This plugin accepts the following configuration options:
|
||||||
|
|
||||||
|
- `orComponent`: If `true` (default), converts Roam `{{ or:ONE|TWO|THREE }}` shortcodes into HTML Dropdown options.
|
||||||
|
- `TODOComponent`: If `true` (default), converts Roam `{{[[TODO]]}}` shortcodes into HTML check boxes.
|
||||||
|
- `DONEComponent`: If `true` (default), converts Roam `{{[[DONE]]}}` shortcodes into checked HTML check boxes.
|
||||||
|
- `videoComponent`: If `true` (default), converts Roam `{{[[video]]:URL}}` shortcodes into embeded HTML video.
|
||||||
|
- `audioComponent`: If `true` (default), converts Roam `{{[[audio]]:URL}}` shortcodes into embeded HTML audio.
|
||||||
|
- `pdfComponent`: If `true` (default), converts Roam `{{[[pdf]]:URL}}` shortcodes into embeded HTML PDF viewer.
|
||||||
|
- `blockquoteComponent`: If `true` (default), converts Roam `{{[[>]]}}` shortcodes into Quartz blockquotes.
|
||||||
|
|
||||||
|
## API
|
||||||
|
|
||||||
|
- Category: Transformer
|
||||||
|
- Function name: `Plugin.RoamFlavoredMarkdown()`.
|
||||||
|
- Source: [`quartz/plugins/transformers/roam.ts`](https://github.com/jackyzha0/quartz/blob/v4/quartz/plugins/transformers/roam.ts).
|
1066
package-lock.json
generated
38
package.json
@ -2,7 +2,7 @@
|
|||||||
"name": "@jackyzha0/quartz",
|
"name": "@jackyzha0/quartz",
|
||||||
"description": "🌱 publish your digital garden and notes as a website",
|
"description": "🌱 publish your digital garden and notes as a website",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "4.3.1",
|
"version": "4.4.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
|
"author": "jackyzha0 <j.zhao2k19@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@ -36,40 +36,40 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clack/prompts": "^0.7.0",
|
"@clack/prompts": "^0.7.0",
|
||||||
"@floating-ui/dom": "^1.6.10",
|
"@floating-ui/dom": "^1.6.11",
|
||||||
"@napi-rs/simple-git": "0.1.19",
|
"@napi-rs/simple-git": "0.1.19",
|
||||||
"@tweenjs/tween.js": "^25.0.0",
|
"@tweenjs/tween.js": "^25.0.0",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"chalk": "^5.3.0",
|
"chalk": "^5.3.0",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^4.0.1",
|
||||||
"cli-spinner": "^0.2.10",
|
"cli-spinner": "^0.2.10",
|
||||||
"d3": "^7.9.0",
|
"d3": "^7.9.0",
|
||||||
"esbuild-sass-plugin": "^2.16.1",
|
"esbuild-sass-plugin": "^3.3.1",
|
||||||
"flexsearch": "0.7.43",
|
"flexsearch": "0.7.43",
|
||||||
"github-slugger": "^2.0.0",
|
"github-slugger": "^2.0.0",
|
||||||
"globby": "^14.0.2",
|
"globby": "^14.0.2",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"hast-util-to-html": "^9.0.1",
|
"hast-util-to-html": "^9.0.3",
|
||||||
"hast-util-to-jsx-runtime": "^2.3.0",
|
"hast-util-to-jsx-runtime": "^2.3.0",
|
||||||
"hast-util-to-string": "^3.0.0",
|
"hast-util-to-string": "^3.0.1",
|
||||||
"is-absolute-url": "^4.0.1",
|
"is-absolute-url": "^4.0.1",
|
||||||
"js-yaml": "^4.1.0",
|
"js-yaml": "^4.1.0",
|
||||||
"lightningcss": "^1.26.0",
|
"lightningcss": "^1.27.0",
|
||||||
"mdast-util-find-and-replace": "^3.0.1",
|
"mdast-util-find-and-replace": "^3.0.1",
|
||||||
"mdast-util-to-hast": "^13.2.0",
|
"mdast-util-to-hast": "^13.2.0",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"micromorph": "^0.4.5",
|
"micromorph": "^0.4.5",
|
||||||
"pixi.js": "^8.3.3",
|
"pixi.js": "^8.4.1",
|
||||||
"preact": "^10.23.2",
|
"preact": "^10.24.2",
|
||||||
"preact-render-to-string": "^6.5.9",
|
"preact-render-to-string": "^6.5.11",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"pretty-time": "^1.1.0",
|
"pretty-time": "^1.1.0",
|
||||||
"reading-time": "^1.5.0",
|
"reading-time": "^1.5.0",
|
||||||
"rehype-autolink-headings": "^7.1.0",
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
"rehype-citation": "^2.1.1",
|
"rehype-citation": "^2.1.2",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-mathjax": "^6.0.0",
|
"rehype-mathjax": "^6.0.0",
|
||||||
"rehype-pretty-code": "^0.13.2",
|
"rehype-pretty-code": "^0.14.0",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"rehype-slug": "^6.0.0",
|
"rehype-slug": "^6.0.0",
|
||||||
"remark": "^15.0.1",
|
"remark": "^15.0.1",
|
||||||
@ -78,18 +78,18 @@
|
|||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"remark-parse": "^11.0.0",
|
"remark-parse": "^11.0.0",
|
||||||
"remark-rehype": "^11.1.0",
|
"remark-rehype": "^11.1.1",
|
||||||
"remark-smartypants": "^3.0.2",
|
"remark-smartypants": "^3.0.2",
|
||||||
"rfdc": "^1.4.1",
|
"rfdc": "^1.4.1",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"serve-handler": "^6.1.5",
|
"serve-handler": "^6.1.5",
|
||||||
"shiki": "^1.12.1",
|
"shiki": "^1.22.0",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"to-vfile": "^8.0.0",
|
"to-vfile": "^8.0.0",
|
||||||
"toml": "^3.0.0",
|
"toml": "^3.0.0",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.0.0",
|
||||||
"vfile": "^6.0.2",
|
"vfile": "^6.0.3",
|
||||||
"workerpool": "^9.1.3",
|
"workerpool": "^9.1.3",
|
||||||
"ws": "^8.18.0",
|
"ws": "^8.18.0",
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
@ -99,14 +99,14 @@
|
|||||||
"@types/d3": "^7.4.3",
|
"@types/d3": "^7.4.3",
|
||||||
"@types/hast": "^3.0.4",
|
"@types/hast": "^3.0.4",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/node": "^22.5.0",
|
"@types/node": "^22.7.5",
|
||||||
"@types/pretty-time": "^1.1.5",
|
"@types/pretty-time": "^1.1.5",
|
||||||
"@types/source-map-support": "^0.5.10",
|
"@types/source-map-support": "^0.5.10",
|
||||||
"@types/ws": "^8.5.12",
|
"@types/ws": "^8.5.12",
|
||||||
"@types/yargs": "^17.0.33",
|
"@types/yargs": "^17.0.33",
|
||||||
"esbuild": "^0.19.9",
|
"esbuild": "^0.24.0",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"tsx": "^4.18.0",
|
"tsx": "^4.19.1",
|
||||||
"typescript": "^5.5.4"
|
"typescript": "^5.6.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import * as Plugin from "./quartz/plugins"
|
|||||||
const config: QuartzConfig = {
|
const config: QuartzConfig = {
|
||||||
configuration: {
|
configuration: {
|
||||||
pageTitle: "🪴 Quartz 4.0",
|
pageTitle: "🪴 Quartz 4.0",
|
||||||
|
pageTitleSuffix: "",
|
||||||
enableSPA: true,
|
enableSPA: true,
|
||||||
enablePopovers: true,
|
enablePopovers: true,
|
||||||
analytics: {
|
analytics: {
|
||||||
|
@ -39,7 +39,7 @@ type BuildData = {
|
|||||||
type FileEvent = "add" | "change" | "delete"
|
type FileEvent = "add" | "change" | "delete"
|
||||||
|
|
||||||
function newBuildId() {
|
function newBuildId() {
|
||||||
return new Date().toISOString()
|
return Math.random().toString(36).substring(2, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
|
async function buildQuartz(argv: Argv, mut: Mutex, clientRefresh: () => void) {
|
||||||
@ -162,17 +162,19 @@ async function partialRebuildFromEntrypoint(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildStart = new Date().getTime()
|
const buildId = newBuildId()
|
||||||
buildData.lastBuildMs = buildStart
|
ctx.buildId = buildId
|
||||||
|
buildData.lastBuildMs = new Date().getTime()
|
||||||
const release = await mut.acquire()
|
const release = await mut.acquire()
|
||||||
if (buildData.lastBuildMs > buildStart) {
|
|
||||||
|
// if there's another build after us, release and let them do it
|
||||||
|
if (ctx.buildId !== buildId) {
|
||||||
release()
|
release()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const perf = new PerfTimer()
|
const perf = new PerfTimer()
|
||||||
console.log(chalk.yellow("Detected change, rebuilding..."))
|
console.log(chalk.yellow("Detected change, rebuilding..."))
|
||||||
ctx.buildId = newBuildId()
|
|
||||||
|
|
||||||
// UPDATE DEP GRAPH
|
// UPDATE DEP GRAPH
|
||||||
const fp = joinSegments(argv.directory, toPosixPath(filepath)) as FilePath
|
const fp = joinSegments(argv.directory, toPosixPath(filepath)) as FilePath
|
||||||
@ -357,19 +359,19 @@ async function rebuildFromEntrypoint(
|
|||||||
toRemove.add(filePath)
|
toRemove.add(filePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildStart = new Date().getTime()
|
const buildId = newBuildId()
|
||||||
buildData.lastBuildMs = buildStart
|
ctx.buildId = buildId
|
||||||
|
buildData.lastBuildMs = new Date().getTime()
|
||||||
const release = await mut.acquire()
|
const release = await mut.acquire()
|
||||||
|
|
||||||
// there's another build after us, release and let them do it
|
// there's another build after us, release and let them do it
|
||||||
if (buildData.lastBuildMs > buildStart) {
|
if (ctx.buildId !== buildId) {
|
||||||
release()
|
release()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const perf = new PerfTimer()
|
const perf = new PerfTimer()
|
||||||
console.log(chalk.yellow("Detected change, rebuilding..."))
|
console.log(chalk.yellow("Detected change, rebuilding..."))
|
||||||
ctx.buildId = newBuildId()
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp))
|
const filesToRebuild = [...toRebuild].filter((fp) => !toRemove.has(fp))
|
||||||
@ -405,10 +407,10 @@ async function rebuildFromEntrypoint(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
release()
|
|
||||||
clientRefresh()
|
clientRefresh()
|
||||||
toRebuild.clear()
|
toRebuild.clear()
|
||||||
toRemove.clear()
|
toRemove.clear()
|
||||||
|
release()
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async (argv: Argv, mut: Mutex, clientRefresh: () => void) => {
|
export default async (argv: Argv, mut: Mutex, clientRefresh: () => void) => {
|
||||||
|
@ -38,9 +38,14 @@ export type Analytics =
|
|||||||
provider: "cabin"
|
provider: "cabin"
|
||||||
host?: string
|
host?: string
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
provider: "clarity"
|
||||||
|
projectId?: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface GlobalConfiguration {
|
export interface GlobalConfiguration {
|
||||||
pageTitle: string
|
pageTitle: string
|
||||||
|
pageTitleSuffix?: string
|
||||||
/** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */
|
/** Whether to enable single-page-app style rendering. this prevents flashes of unstyled content and improves smoothness of Quartz */
|
||||||
enableSPA: boolean
|
enableSPA: boolean
|
||||||
/** Whether to display Wikipedia-style popovers when hovering over links */
|
/** Whether to display Wikipedia-style popovers when hovering over links */
|
||||||
|
@ -12,24 +12,20 @@ const Backlinks: QuartzComponent = ({
|
|||||||
}: QuartzComponentProps) => {
|
}: QuartzComponentProps) => {
|
||||||
const slug = simplifySlug(fileData.slug!)
|
const slug = simplifySlug(fileData.slug!)
|
||||||
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
|
const backlinkFiles = allFiles.filter((file) => file.links?.includes(slug))
|
||||||
return (
|
return backlinkFiles.length <= 0 ? null : (
|
||||||
<div class={classNames(displayClass, "backlinks")}>
|
<div class={classNames(displayClass, "backlinks")}>
|
||||||
<h3>{i18n(cfg.locale).components.backlinks.title}</h3>
|
<h3>{i18n(cfg.locale).components.backlinks.title}</h3>
|
||||||
<ul class="overflow">
|
<ul class="overflow">
|
||||||
{backlinkFiles.length > 0 ? (
|
{backlinkFiles.map((f) => (
|
||||||
backlinkFiles.map((f) => (
|
<li key={f.slug}>
|
||||||
<li>
|
<a href={resolveRelative(fileData.slug!, f.slug!)} class="internal">
|
||||||
<a href={resolveRelative(fileData.slug!, f.slug!)} class="internal">
|
{f.frontmatter?.title}
|
||||||
{f.frontmatter?.title}
|
</a>
|
||||||
</a>
|
</li>
|
||||||
</li>
|
))}
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<li>{i18n(cfg.locale).components.backlinks.noBacklinksFound}</li>
|
|
||||||
)}
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Backlinks.css = style
|
Backlinks.css = style
|
||||||
|
@ -30,7 +30,8 @@ export default ((opts?: Partial<ContentMetaOptions>) => {
|
|||||||
const segments: (string | JSX.Element)[] = []
|
const segments: (string | JSX.Element)[] = []
|
||||||
|
|
||||||
if (fileData.dates) {
|
if (fileData.dates) {
|
||||||
segments.push(formatDate(getDate(cfg, fileData)!, cfg.locale))
|
segments.push("Обновлена: " + formatDate(fileData.dates.modified!, cfg.locale))
|
||||||
|
segments.push("Создана: " + formatDate(fileData.dates.created!, cfg.locale))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display reading time if enabled
|
// Display reading time if enabled
|
||||||
|
@ -2,6 +2,7 @@ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } fro
|
|||||||
import style from "./styles/footer.scss"
|
import style from "./styles/footer.scss"
|
||||||
import { version } from "../../package.json"
|
import { version } from "../../package.json"
|
||||||
import { i18n } from "../i18n"
|
import { i18n } from "../i18n"
|
||||||
|
import script from "./scripts/_randomPage.inline"
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
links: Record<string, string>
|
links: Record<string, string>
|
||||||
@ -14,8 +15,7 @@ export default ((opts?: Options) => {
|
|||||||
return (
|
return (
|
||||||
<footer class={`${displayClass ?? ""}`}>
|
<footer class={`${displayClass ?? ""}`}>
|
||||||
<p>
|
<p>
|
||||||
{i18n(cfg.locale).components.footer.createdWith}{" "}
|
Автор: <a href="https://mark.struchkov.dev">Стручков Марк</a>, если не указано иное. © {year}
|
||||||
<a href="https://quartz.jzhao.xyz/">Quartz v{version}</a> © {year}
|
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
{Object.entries(links).map(([text, link]) => (
|
{Object.entries(links).map(([text, link]) => (
|
||||||
@ -24,10 +24,25 @@ export default ((opts?: Options) => {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
<hr style="margin-top: 1rem;" />
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="#">
|
||||||
|
Наверх ↑
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a id="random-page-button">
|
||||||
|
Мне повезет 🎲
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<script src="static/share.js"></script>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Footer.css = style
|
Footer.css = style
|
||||||
|
Footer.afterDOMLoaded = script
|
||||||
return Footer
|
return Footer
|
||||||
}) satisfies QuartzComponentConstructor
|
}) satisfies QuartzComponentConstructor
|
||||||
|
@ -6,7 +6,9 @@ import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } fro
|
|||||||
|
|
||||||
export default (() => {
|
export default (() => {
|
||||||
const Head: QuartzComponent = ({ cfg, fileData, externalResources }: QuartzComponentProps) => {
|
const Head: QuartzComponent = ({ cfg, fileData, externalResources }: QuartzComponentProps) => {
|
||||||
const title = fileData.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title
|
const titleSuffix = cfg.pageTitleSuffix ?? ""
|
||||||
|
const title =
|
||||||
|
(fileData.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title) + titleSuffix
|
||||||
const description =
|
const description =
|
||||||
fileData.description?.trim() ?? i18n(cfg.locale).propertyDefaults.description
|
fileData.description?.trim() ?? i18n(cfg.locale).propertyDefaults.description
|
||||||
const { css, js } = externalResources
|
const { css, js } = externalResources
|
||||||
@ -16,7 +18,7 @@ export default (() => {
|
|||||||
const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!)
|
const baseDir = fileData.slug === "404" ? path : pathToRoot(fileData.slug!)
|
||||||
|
|
||||||
const iconPath = joinSegments(baseDir, "static/icon.png")
|
const iconPath = joinSegments(baseDir, "static/icon.png")
|
||||||
const ogImagePath = `https://${cfg.baseUrl}/static/og-image.png`
|
const ogImagePath = `https://${cfg.baseUrl}/static/og-image.webp`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<head>
|
<head>
|
||||||
@ -37,7 +39,6 @@ export default (() => {
|
|||||||
<meta property="og:height" content="675" />
|
<meta property="og:height" content="675" />
|
||||||
<link rel="icon" href={iconPath} />
|
<link rel="icon" href={iconPath} />
|
||||||
<meta name="description" content={description} />
|
<meta name="description" content={description} />
|
||||||
<meta name="generator" content="Quartz" />
|
|
||||||
{css.map((href) => (
|
{css.map((href) => (
|
||||||
<link key={href} href={href} rel="stylesheet" type="text/css" spa-preserve />
|
<link key={href} href={href} rel="stylesheet" type="text/css" spa-preserve />
|
||||||
))}
|
))}
|
||||||
|
@ -37,7 +37,7 @@ export default ((userOpts?: Partial<Options>) => {
|
|||||||
const remaining = Math.max(0, pages.length - opts.limit)
|
const remaining = Math.max(0, pages.length - opts.limit)
|
||||||
return (
|
return (
|
||||||
<div class={classNames(displayClass, "recent-notes")}>
|
<div class={classNames(displayClass, "recent-notes")}>
|
||||||
<h3>{opts.title ?? i18n(cfg.locale).components.recentNotes.title}</h3>
|
<h4>{opts.title ?? i18n(cfg.locale).components.recentNotes.title}</h4>
|
||||||
<ul class="recent-ul">
|
<ul class="recent-ul">
|
||||||
{pages.slice(0, opts.limit).map((page) => {
|
{pages.slice(0, opts.limit).map((page) => {
|
||||||
const title = page.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title
|
const title = page.frontmatter?.title ?? i18n(cfg.locale).propertyDefaults.title
|
||||||
@ -47,11 +47,11 @@ export default ((userOpts?: Partial<Options>) => {
|
|||||||
<li class="recent-li">
|
<li class="recent-li">
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="desc">
|
<div class="desc">
|
||||||
<h3>
|
<span>
|
||||||
<a href={resolveRelative(fileData.slug!, page.slug!)} class="internal">
|
<a href={resolveRelative(fileData.slug!, page.slug!)} class="internal">
|
||||||
{title}
|
{title}
|
||||||
</a>
|
</a>
|
||||||
</h3>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{page.dates && (
|
{page.dates && (
|
||||||
<p class="meta">
|
<p class="meta">
|
||||||
|
21
quartz/components/_Ads.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
// @ts-ignore
|
||||||
|
import { classNames } from "../util/lang"
|
||||||
|
|
||||||
|
const Ads: QuartzComponent = ({ displayClass, fileData }: QuartzComponentProps) => {
|
||||||
|
return (
|
||||||
|
<blockquote className="callout tip desktop-only" data-callout="tip">
|
||||||
|
<div className="callout-title">
|
||||||
|
<div className="callout-icon"></div>
|
||||||
|
<div className="callout-title-inner"><p>Спонсор: VDSina</p></div>
|
||||||
|
</div>
|
||||||
|
<div className="callout-content">
|
||||||
|
<p>Хостинг провайдер, которым пользуюсь сам. Сервера на территории РФ и Нидерландов, 2 ТБ трафика на
|
||||||
|
сервер.<br />–
|
||||||
|
– – – –<br /><a href="https://vdsina.ru/?partner=3yw9q78nd5" rel="nofollow">Вечная скидка 10% при
|
||||||
|
регистрации.</a></p>
|
||||||
|
</div>
|
||||||
|
</blockquote>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default (() => Ads) satisfies QuartzComponentConstructor
|
52
quartz/components/_GithubSource.tsx
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
import style from "./styles/_githubSource.scss"
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import { classNames } from "../util/lang"
|
||||||
|
|
||||||
|
interface GithubSourceOptions {
|
||||||
|
repoLink: string
|
||||||
|
branch: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: GithubSourceOptions = {
|
||||||
|
repoLink: "github.com",
|
||||||
|
branch: "v4"
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ((opts?: Partial<GithubSourceOptions>) => {
|
||||||
|
// Merge options with defaults
|
||||||
|
const options: GithubSourceOptions = { ...defaultOptions, ...opts }
|
||||||
|
const GithubSource: QuartzComponent = ({
|
||||||
|
displayClass,
|
||||||
|
fileData
|
||||||
|
}: QuartzComponentProps) => {
|
||||||
|
return (
|
||||||
|
<div class={classNames(displayClass, "github-source")}>
|
||||||
|
<h3>Инструменты</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href={`${options?.repoLink}/blob/${options?.branch}/${fileData.filePath!.replace(/^content\//, "")}`}
|
||||||
|
className="external">
|
||||||
|
✏️ Редактировать
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href={`${options?.repoLink}/commits/${options?.branch}/${fileData.filePath!.replace(/^content\//, "")}`}
|
||||||
|
className="external">
|
||||||
|
🕒 История изменений
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href={`${options?.repoLink}/blame/${options?.branch}/${fileData.filePath!.replace(/^content\//, "")}`}
|
||||||
|
className="external">
|
||||||
|
🧑💻 Авторы
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
GithubSource.css = style
|
||||||
|
return GithubSource
|
||||||
|
}) satisfies QuartzComponentConstructor
|
33
quartz/components/_RandomPageButton.tsx
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
// @ts-ignore
|
||||||
|
import script from "./scripts/_randomPage.inline"
|
||||||
|
import style from "./styles/_randomPage.scss"
|
||||||
|
import { classNames } from "../util/lang"
|
||||||
|
|
||||||
|
const RandomPageButton: QuartzComponent = ({ displayClass, fileData }: QuartzComponentProps) => {
|
||||||
|
return (
|
||||||
|
<div id="random-page-button" class={classNames(displayClass, "random-page")}>
|
||||||
|
{/* <ul>
|
||||||
|
<li> */}
|
||||||
|
<svg y="0px" x="0px" viewBox="0 0 316 316">
|
||||||
|
<title>Random page</title>
|
||||||
|
<g>
|
||||||
|
<rect class="random-page-square" stroke-width="15" rx="30" height="300" width="300" y="8" x="8" fill="none" />
|
||||||
|
<ellipse class="random-page-ellipse" ry="20" rx="20" cy="83" cx="108" />
|
||||||
|
<ellipse class="random-page-ellipse" ry="20" rx="20" cy="83" cx="208" />
|
||||||
|
<ellipse class="random-page-ellipse" ry="20" rx="20" cy="233" cx="208" />
|
||||||
|
<ellipse class="random-page-ellipse" ry="20" rx="20" cy="233" cx="108" />
|
||||||
|
<ellipse class="random-page-ellipse" ry="20" rx="20" cy="158" cx="208" />
|
||||||
|
<ellipse class="random-page-ellipse" ry="20" rx="20" cy="158" cx="108" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
{/* </li>
|
||||||
|
</ul> */}
|
||||||
|
|
||||||
|
{/* <h3>Go to a random page 🎲</h3> */}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RandomPageButton.css = style
|
||||||
|
RandomPageButton.afterDOMLoaded = script
|
||||||
|
export default (() => RandomPageButton) satisfies QuartzComponentConstructor
|
74
quartz/components/_Remark.tsx
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
// @ts-ignore
|
||||||
|
// import script from "./scripts/comments.inline"
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
options: {
|
||||||
|
host: string
|
||||||
|
siteId: string
|
||||||
|
locale: string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// function boolToStringBool(b: boolean): string {
|
||||||
|
// return b ? "1" : "0"
|
||||||
|
// }
|
||||||
|
|
||||||
|
export default ((opts: Options) => {
|
||||||
|
const Remark: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
|
||||||
|
const remarkConfig = `
|
||||||
|
var remark_config = {
|
||||||
|
host: "${opts.options.host}",
|
||||||
|
site_id: "${opts.options.siteId}",
|
||||||
|
components: ["embed"],
|
||||||
|
locale: "${opts.options.locale}",
|
||||||
|
theme: localStorage.theme,
|
||||||
|
show_email_subscription: false,
|
||||||
|
simple_view: false
|
||||||
|
};
|
||||||
|
`
|
||||||
|
|
||||||
|
const embedScript = `
|
||||||
|
!function(e,n){
|
||||||
|
for(var o=0;o<e.length;o++){
|
||||||
|
var r=n.createElement("script"),
|
||||||
|
c=".js",
|
||||||
|
d=n.head||n.body;
|
||||||
|
"noModule" in r ? (r.type="module",c=".mjs") : r.async=!0,
|
||||||
|
r.defer=!0,
|
||||||
|
r.src=remark_config.host+"/web/"+e[o]+c,
|
||||||
|
d.appendChild(r)
|
||||||
|
}
|
||||||
|
}(remark_config.components||["embed"], document);
|
||||||
|
`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<hr />
|
||||||
|
<a className="notice notice-telegram" href="https://t.me/struchkov_dev" target="_blank">
|
||||||
|
<div className="notice__head">
|
||||||
|
<svg className="notice__head-icon" fill="currentColor" viewBox="0 0 190 170">
|
||||||
|
<path
|
||||||
|
d="M152.531 170c-1.48 0-2.95-.438-4.21-1.293l-47.642-32.316-25.552 18.386a7.502 7.502 0 01-11.633-4.174l-12.83-48.622L4.821 84.452a7.501 7.501 0 01-.094-13.975L179.04 1.117a7.503 7.503 0 0110.282 8.408l-29.423 154.379a7.499 7.499 0 01-7.367 6.096zm-47.669-48.897l42.437 28.785 22.894-120.124-82.687 79.566 17.156 11.638c.07.043.135.089.2.135zm-35.327-6.401l5.682 21.53 12.242-8.809-16.03-10.874a7.478 7.478 0 01-1.894-1.847zM28.136 77.306l31.478 12.035a7.5 7.5 0 014.573 5.092l3.992 15.129a7.504 7.504 0 012.26-4.624l78.788-75.814z"></path>
|
||||||
|
</svg>
|
||||||
|
<div className="notice__head-text">Подписывайся</div>
|
||||||
|
</div>
|
||||||
|
<div className="notice__tail">
|
||||||
|
<div className="notice__tail-text">на Struchkov.Dev в Telegram</div>
|
||||||
|
</div>
|
||||||
|
<div className="notice__skin">
|
||||||
|
<svg className="notice__skin-icon" fill="currentColor" viewBox="0 0 448 376">
|
||||||
|
<path
|
||||||
|
d="M446.684 34.522l-67.6 318.8c-5.1 22.5-18.4 28.1-37.3 17.5l-103-75.9-49.7 47.8c-5.5 5.5-10.1 10.1-20.7 10.1l7.4-104.9 190.9-172.5c8.3-7.4-1.8-11.5-12.9-4.1l-236 148.6-101.6-31.8c-22.1-6.9-22.5-22.1 4.6-32.7l397.4-153.1c18.4-6.9 34.5 4.1 28.5 32.2z"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div id="remark42" style="margin-top: 2rem"></div>
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: remarkConfig }}></script>
|
||||||
|
<script dangerouslySetInnerHTML={{ __html: embedScript }}></script>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Remark
|
||||||
|
}) satisfies QuartzComponentConstructor<Options>
|
31
quartz/components/_ScrollToTop.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
import style from "./styles/_scrollToTop.scss"
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import script from "./scripts/_randomPage.inline"
|
||||||
|
import { classNames } from "../util/lang"
|
||||||
|
import { i18n } from "../i18n"
|
||||||
|
|
||||||
|
const ScrollToTop: QuartzComponent = ({ displayClass, fileData }: QuartzComponentProps) => {
|
||||||
|
return (
|
||||||
|
<div class={classNames(displayClass, "scroll-to-top")}>
|
||||||
|
<h3>Utilities</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="#">
|
||||||
|
Scroll to top ↑
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a id="random-page-button">
|
||||||
|
Random Page 🎲
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
ScrollToTop.css = style
|
||||||
|
ScrollToTop.afterDOMLoaded = script
|
||||||
|
export default (() => ScrollToTop) satisfies QuartzComponentConstructor
|
78
quartz/components/_SocialShare.tsx
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
|
||||||
|
// @ts-ignore
|
||||||
|
import style from "./styles/_socialshare.scss"
|
||||||
|
|
||||||
|
const SocialShare: QuartzComponent = ({ displayClass, fileData }: QuartzComponentProps) => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="post-share">
|
||||||
|
<div className="share-link">
|
||||||
|
<a className="share-icon share-telegram"
|
||||||
|
href="javascript:void(0);"
|
||||||
|
title="Поделиться в Telegram"
|
||||||
|
data-sharer="telegram"
|
||||||
|
data-url="{{url absolute=" true"}}"
|
||||||
|
data-title="{{title}}"
|
||||||
|
data-caption="{{custom_excerpt}}">
|
||||||
|
<span className="svg-social-icon icon-telegram"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="share-link">
|
||||||
|
<a className="share-icon share-twitter"
|
||||||
|
href="javascript:void(0);"
|
||||||
|
title="Поделиться в Twitter"
|
||||||
|
data-sharer="twitter"
|
||||||
|
data-url="{{url absolute=" true"}}"
|
||||||
|
data-title="{{title}}">
|
||||||
|
<span className="svg-social-icon icon-twitter"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="share-link">-->
|
||||||
|
<!-- <a class="share-icon share-whatsapp"-->
|
||||||
|
<!-- href="javascript:void(0);"-->
|
||||||
|
<!-- title="Поделиться в WhatsApp"-->
|
||||||
|
<!-- data-sharer="whatsapp"-->
|
||||||
|
<!-- data-url="{{url absolute="true"}}"-->
|
||||||
|
<!-- data-title="{{title}}"-->
|
||||||
|
<!-- data-web="">-->
|
||||||
|
<!-- <span class="svg-social-icon icon-whatsapp"></span>-->
|
||||||
|
<!-- </a>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- <div class="share-link">-->
|
||||||
|
<!-- <a class="share-icon share-vk"-->
|
||||||
|
<!-- href="javascript:void(0);"-->
|
||||||
|
<!-- title="Поделиться в VK"-->
|
||||||
|
<!-- data-sharer="vk"-->
|
||||||
|
<!-- data-url="{{url absolute="true"}}"-->
|
||||||
|
<!-- data-title="{{title}}"-->
|
||||||
|
<!-- data-caption="{{custom_excerpt}}">-->
|
||||||
|
<!-- <span class="svg-social-icon icon-vk"></span>-->
|
||||||
|
<!-- </a>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<div className="share-link">
|
||||||
|
<a className="share-icon share-pocket"
|
||||||
|
href="javascript:void(0);"
|
||||||
|
title="Поделиться в Pocket"
|
||||||
|
data-title="{{title}}"
|
||||||
|
data-sharer="pocket"
|
||||||
|
data-url="{{url absolute=" true"}}">
|
||||||
|
<span className="svg-social-icon icon-pocket"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div className="share-link">
|
||||||
|
<a className="share-icon share-skype"
|
||||||
|
href="javascript:void(0);"
|
||||||
|
title="Поделиться в Skype"
|
||||||
|
data-sharer="skype"
|
||||||
|
data-url="{{url absolute=" true"}}"
|
||||||
|
data-title="{{title}}">
|
||||||
|
<span className="svg-social-icon icon-skype"></span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
SocialShare.css = style;
|
||||||
|
export default (() => SocialShare) satisfies QuartzComponentConstructor;
|
34
quartz/components/_YandexMetrika.tsx
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types";
|
||||||
|
|
||||||
|
const YandexMetrika: QuartzComponent = ({ displayClass, cfg }: QuartzComponentProps) => {
|
||||||
|
const embedScript = `
|
||||||
|
(function(m,e,t,r,i,k,a){m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
||||||
|
m[i].l=1*new Date();
|
||||||
|
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
|
||||||
|
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)})
|
||||||
|
(window, document, "script", "https://mc.yandex.ru/metrika/tag.js", "ym");
|
||||||
|
|
||||||
|
ym(98254050, "init", {
|
||||||
|
clickmap: true,
|
||||||
|
trackLinks: true,
|
||||||
|
accurateTrackBounce: true
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<script type="text/javascript" dangerouslySetInnerHTML={{ __html: embedScript }}></script>
|
||||||
|
<noscript>
|
||||||
|
<div>
|
||||||
|
<img
|
||||||
|
src="https://mc.yandex.ru/watch/98254050"
|
||||||
|
style={{ position: "absolute", left: "-9999px" }}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</noscript>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (() => YandexMetrika) satisfies QuartzComponentConstructor;
|
@ -20,6 +20,13 @@ import MobileOnly from "./MobileOnly"
|
|||||||
import RecentNotes from "./RecentNotes"
|
import RecentNotes from "./RecentNotes"
|
||||||
import Breadcrumbs from "./Breadcrumbs"
|
import Breadcrumbs from "./Breadcrumbs"
|
||||||
import Comments from "./Comments"
|
import Comments from "./Comments"
|
||||||
|
import Remark from "./_Remark"
|
||||||
|
import ScrollToTop from "./_ScrollToTop"
|
||||||
|
import GithubSource from "./_GithubSource"
|
||||||
|
import RandomPageButton from "./_RandomPageButton"
|
||||||
|
import Ads from "./_Ads"
|
||||||
|
import YandexMetrika from "./_YandexMetrika"
|
||||||
|
// import SocialShare from "./_SocialShare"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
ArticleTitle,
|
ArticleTitle,
|
||||||
@ -44,4 +51,11 @@ export {
|
|||||||
NotFound,
|
NotFound,
|
||||||
Breadcrumbs,
|
Breadcrumbs,
|
||||||
Comments,
|
Comments,
|
||||||
|
Remark,
|
||||||
|
GithubSource,
|
||||||
|
ScrollToTop,
|
||||||
|
RandomPageButton,
|
||||||
|
Ads,
|
||||||
|
YandexMetrika,
|
||||||
|
// SocialShare
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ const NotFound: QuartzComponent = ({ cfg }: QuartzComponentProps) => {
|
|||||||
<h1>404</h1>
|
<h1>404</h1>
|
||||||
<p>{i18n(cfg.locale).pages.error.notFound}</p>
|
<p>{i18n(cfg.locale).pages.error.notFound}</p>
|
||||||
<a href={baseDir}>{i18n(cfg.locale).pages.error.home}</a>
|
<a href={baseDir}>{i18n(cfg.locale).pages.error.home}</a>
|
||||||
|
<pre><code> ██████████ _________________<br/> ██░░░░░░░░░░██ | You got lost... |<br/> ██░░░░░░░░░░░░░░██ 🗩 ‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾<br/> ██░░██░░░░░░██░░░░░░██ 🗩<br/> ██░░██░░░░░░██░░▒▒░░██<br/> ██░░░░░░░░░░░░░░▒▒░░██<br/> ██▒▒▒▒▒▒▒▒▒▒▒▒░░██<br/> ██████████████<br/> ████░░░░██░░░░▒▒████<br/> ██░░░░░░██░░░░░░▒▒██▒▒██<br/>██░░░░██▒▒▒▒▒▒▒▒▒▒██░░▒▒▒▒██<br/>██▒▒░░░░██████████░░░░▒▒██▒▒██<br/>██▒▒░░░░░░░░░░░░░░░░░░▒▒██▒▒██<br/> ██▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒████▒▒██<br/> ██████████████████ ██<br/></code></pre>
|
||||||
</article>
|
</article>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,6 @@ export function renderPage(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Content {...componentData} />
|
<Content {...componentData} />
|
||||||
<hr />
|
|
||||||
<div class="page-footer">
|
<div class="page-footer">
|
||||||
{afterBody.map((BodyComponent) => (
|
{afterBody.map((BodyComponent) => (
|
||||||
<BodyComponent {...componentData} />
|
<BodyComponent {...componentData} />
|
||||||
@ -242,8 +241,8 @@ export function renderPage(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{RightComponent}
|
{RightComponent}
|
||||||
|
<Footer {...componentData} />
|
||||||
</Body>
|
</Body>
|
||||||
<Footer {...componentData} />
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
{pageResources.js
|
{pageResources.js
|
||||||
|
28
quartz/components/scripts/_randomPage.inline.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { FullSlug, getFullSlug, pathToRoot, simplifySlug } from "../../util/path"
|
||||||
|
|
||||||
|
function getRandomInt(max: number) {
|
||||||
|
return Math.floor(Math.random() * max);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function navigateToRandomPage() {
|
||||||
|
const fullSlug = getFullSlug(window)
|
||||||
|
const data = await fetchData
|
||||||
|
const allPosts = Object.keys(data).map((slug) => simplifySlug(slug as FullSlug))
|
||||||
|
// window.location.href = `${pathToRoot(fullSlug)}/${allPosts[getRandomInt(allPosts.length - 1)]}`
|
||||||
|
let newSlug = `${pathToRoot(fullSlug)}/${allPosts[getRandomInt(allPosts.length - 1)]}`;
|
||||||
|
|
||||||
|
if (newSlug === fullSlug) {
|
||||||
|
// Generate a new random slug until it's different from the starting fullSlug
|
||||||
|
do {
|
||||||
|
newSlug = `${pathToRoot(fullSlug)}/${allPosts[getRandomInt(allPosts.length - 1)]}`;
|
||||||
|
} while (newSlug === fullSlug);
|
||||||
|
}
|
||||||
|
window.location.href = newSlug;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("nav", async (e: unknown) => {
|
||||||
|
// const slug = (e as CustomEventMap["nav"]).detail.url
|
||||||
|
const button = document.getElementById("random-page-button")
|
||||||
|
button?.removeEventListener("click", navigateToRandomPage)
|
||||||
|
button?.addEventListener("click", navigateToRandomPage)
|
||||||
|
})
|
@ -25,7 +25,6 @@ function toggleExplorer(this: HTMLElement) {
|
|||||||
if (!content) return
|
if (!content) return
|
||||||
|
|
||||||
content.classList.toggle("collapsed")
|
content.classList.toggle("collapsed")
|
||||||
content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFolder(evt: MouseEvent) {
|
function toggleFolder(evt: MouseEvent) {
|
||||||
|
@ -23,7 +23,6 @@ function toggleToc(this: HTMLElement) {
|
|||||||
const content = this.nextElementSibling as HTMLElement | undefined
|
const content = this.nextElementSibling as HTMLElement | undefined
|
||||||
if (!content) return
|
if (!content) return
|
||||||
content.classList.toggle("collapsed")
|
content.classList.toggle("collapsed")
|
||||||
content.style.maxHeight = content.style.maxHeight === "0px" ? content.scrollHeight + "px" : "0px"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupToc() {
|
function setupToc() {
|
||||||
@ -32,7 +31,6 @@ function setupToc() {
|
|||||||
const collapsed = toc.classList.contains("collapsed")
|
const collapsed = toc.classList.contains("collapsed")
|
||||||
const content = toc.nextElementSibling as HTMLElement | undefined
|
const content = toc.nextElementSibling as HTMLElement | undefined
|
||||||
if (!content) return
|
if (!content) return
|
||||||
content.style.maxHeight = collapsed ? "0px" : content.scrollHeight + "px"
|
|
||||||
toc.addEventListener("click", toggleToc)
|
toc.addEventListener("click", toggleToc)
|
||||||
window.addCleanup(() => toc.removeEventListener("click", toggleToc))
|
window.addCleanup(() => toc.removeEventListener("click", toggleToc))
|
||||||
}
|
}
|
||||||
|
27
quartz/components/styles/_githubSource.scss
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
.github-source {
|
||||||
|
&>h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&>ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
|
||||||
|
&>li {
|
||||||
|
&>a {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.external-icon {
|
||||||
|
height: 1ex;
|
||||||
|
margin: 0 0.15em;
|
||||||
|
|
||||||
|
> path {
|
||||||
|
fill: var(--dark);
|
||||||
|
}
|
||||||
|
}
|
41
quartz/components/styles/_randomPage.scss
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
.random-page {
|
||||||
|
position: relative;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-left: auto;
|
||||||
|
margin: 0 10px;
|
||||||
|
|
||||||
|
&>ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
> svg {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
top: calc(50% - 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.7; /* Decrease opacity on hover */
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
&>h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-page-ellipse {
|
||||||
|
stroke: var(--darkgray);
|
||||||
|
fill: var(--darkgray);
|
||||||
|
transition: stroke 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.random-page-square {
|
||||||
|
stroke: var(--darkgray);
|
||||||
|
transition: stroke 0.5s ease;
|
||||||
|
}
|
31
quartz/components/styles/_scrollToTop.scss
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
.scroll-to-top {
|
||||||
|
&>h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&>ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
|
||||||
|
&>li {
|
||||||
|
&>a {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.external-icon {
|
||||||
|
height: 1ex;
|
||||||
|
margin: 0 0.15em;
|
||||||
|
|
||||||
|
> path {
|
||||||
|
fill: var(--dark);
|
||||||
|
}
|
||||||
|
}
|
507
quartz/components/styles/_socialshare.scss
Normal file
@ -0,0 +1,507 @@
|
|||||||
|
a.share-icon {
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.post-share {
|
||||||
|
|
||||||
|
a.share-icon:hover {
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
.svg-social-icon {
|
||||||
|
background-image: var(--sprite-share-icon-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-link {
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-right: 8px;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
a.share-telegram {
|
||||||
|
background: #2CA5E0;
|
||||||
|
border-color: #2CA5E0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-twitter {
|
||||||
|
background: #1DA1F2;
|
||||||
|
border-color: #1DA1F2;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-vk {
|
||||||
|
background: #4680C2;
|
||||||
|
border-color: #4680C2;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-whatsapp {
|
||||||
|
background: #25D366;
|
||||||
|
border-color: #25D366;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-pocket {
|
||||||
|
background: #EF3F56;
|
||||||
|
border-color: #EF3F56;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-linkedin {
|
||||||
|
background: #0077B5;
|
||||||
|
border-color: #0077B5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-viber {
|
||||||
|
background: #665CAC;
|
||||||
|
border-color: #665CAC;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-pinterest {
|
||||||
|
background: #BD081C;
|
||||||
|
border-color: #BD081C;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-tumblr {
|
||||||
|
background: #36465D;
|
||||||
|
border-color: #36465D;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-reddit {
|
||||||
|
background: #FF4500;
|
||||||
|
border-color: #FF4500;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-buffer {
|
||||||
|
background: #168EEA;
|
||||||
|
border-color: #168EEA;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-xing {
|
||||||
|
background: #006567;
|
||||||
|
border-color: #006567;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-line {
|
||||||
|
background: #00C300;
|
||||||
|
border-color: #00C300;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-instapaper {
|
||||||
|
background: #1F1F1F;
|
||||||
|
border-color: #1F1F1F;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-digg {
|
||||||
|
background: #000000;
|
||||||
|
border-color: #000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-stumbleupon {
|
||||||
|
background: #FD8235;
|
||||||
|
border-color: #FD8235;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-flipboard {
|
||||||
|
background: #E12828;
|
||||||
|
border-color: #E12828;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-weibo {
|
||||||
|
background: #20B8E5;
|
||||||
|
border-color: #20B8E5;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-renren {
|
||||||
|
background: #217DC6;
|
||||||
|
border-color: #217DC6;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-myspace {
|
||||||
|
background: #030303;
|
||||||
|
border-color: #030303;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-blogger {
|
||||||
|
background: #FF5722;
|
||||||
|
border-color: #FF5722;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-baidu {
|
||||||
|
background: #2319DC;
|
||||||
|
border-color: #2319DC;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-ok {
|
||||||
|
background: #EE8208;
|
||||||
|
border-color: #EE8208;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-evernote {
|
||||||
|
background: #00A82D;
|
||||||
|
border-color: #00A82D;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-skype {
|
||||||
|
background: #00AFF0;
|
||||||
|
border-color: #00AFF0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-trello {
|
||||||
|
background: #0079BF;
|
||||||
|
border-color: #0079BF;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-mix {
|
||||||
|
background: #FF8126;
|
||||||
|
border-color: #FF8126;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-hackernews {
|
||||||
|
background: #FF8126;
|
||||||
|
border-color: #FF8126;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-text {
|
||||||
|
font-size: 30px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.share-icon {
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px 12px 4px 12px;
|
||||||
|
border: 2px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-social-icon {
|
||||||
|
display: inline-block;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: var(--sprite-share-icon);
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-social-icon-color {
|
||||||
|
display: inline-block;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: var(--sprite-share-icon-color);
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-rss {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-mail-dot-ru {
|
||||||
|
background-position: -25px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-udemy {
|
||||||
|
background-position: -50px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-discord {
|
||||||
|
background-position: -75px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-docker {
|
||||||
|
background-position: -100px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-matrix {
|
||||||
|
background-position: -125px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-xmpp {
|
||||||
|
background-position: -150px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-gitea {
|
||||||
|
background-position: -175px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-mastodon {
|
||||||
|
background-position: -200px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-researchgate {
|
||||||
|
background-position: -225px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-google {
|
||||||
|
background-position: 0 -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-vine {
|
||||||
|
background-position: -25px -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-wordpress {
|
||||||
|
background-position: -50px -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-dribbble {
|
||||||
|
background-position: -75px -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-behance {
|
||||||
|
background-position: -100px -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-deviantart {
|
||||||
|
background-position: -125px -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-jsfiddle {
|
||||||
|
background-position: -150px -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-angellist {
|
||||||
|
background-position: -175px -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-zhihu {
|
||||||
|
background-position: -200px -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-strava {
|
||||||
|
background-position: -225px -25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-twitch {
|
||||||
|
background-position: 0 -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-steam {
|
||||||
|
background-position: -25px -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-patreon {
|
||||||
|
background-position: -50px -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-kickstarter {
|
||||||
|
background-position: -75px -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-foursquare {
|
||||||
|
background-position: -100px -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-last-dot-fm {
|
||||||
|
background-position: -125px -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-goodreads {
|
||||||
|
background-position: -150px -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-500px {
|
||||||
|
background-position: -175px -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-paypal {
|
||||||
|
background-position: -200px -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-bandcamp {
|
||||||
|
background-position: -225px -50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-spotify {
|
||||||
|
background-position: 0 -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-soundcloud {
|
||||||
|
background-position: -25px -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-snapchat {
|
||||||
|
background-position: -50px -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-xing {
|
||||||
|
background-position: -75px -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-flickr {
|
||||||
|
background-position: -100px -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-stackoverflow {
|
||||||
|
background-position: -125px -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-bitbucket {
|
||||||
|
background-position: -150px -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-freecodecamp {
|
||||||
|
background-position: -175px -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-codepen {
|
||||||
|
background-position: -200px -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-pinterest {
|
||||||
|
background-position: -225px -75px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-keybase {
|
||||||
|
background-position: 0 -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-quora {
|
||||||
|
background-position: -25px -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tumblr {
|
||||||
|
background-position: -50px -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-youtube {
|
||||||
|
background-position: -75px -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-gitlab {
|
||||||
|
background-position: -100px -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-medium {
|
||||||
|
background-position: -125px -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-instagram {
|
||||||
|
background-position: -150px -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-linkedin {
|
||||||
|
background-position: -175px -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-github {
|
||||||
|
background-position: -200px -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-mixer {
|
||||||
|
background-position: -225px -100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-trello {
|
||||||
|
background-position: 0 -125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-skype {
|
||||||
|
background-position: -25px -125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-evernote {
|
||||||
|
background-position: -50px -125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-odnoklassniki {
|
||||||
|
background-position: -75px -125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-baidu {
|
||||||
|
background-position: -100px -125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-blogger {
|
||||||
|
background-position: -125px -125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-myspace {
|
||||||
|
background-position: -150px -125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-renren {
|
||||||
|
background-position: -175px -125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-sinaweibo {
|
||||||
|
background-position: -200px -125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-flipboard {
|
||||||
|
background-position: -225px -125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-digg {
|
||||||
|
background-position: 0 -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-pocket {
|
||||||
|
background-position: -25px -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-instapaper {
|
||||||
|
background-position: -50px -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-line {
|
||||||
|
background-position: -75px -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-xing1 {
|
||||||
|
background-position: -100px -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-buffer {
|
||||||
|
background-position: -125px -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-reddit {
|
||||||
|
background-position: -150px -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tumblr1 {
|
||||||
|
background-position: -175px -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-pinterest1 {
|
||||||
|
background-position: -200px -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-viber {
|
||||||
|
background-position: -225px -150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-linkedin1 {
|
||||||
|
background-position: 0 -175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-telegram {
|
||||||
|
background-position: -25px -175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-whatsapp {
|
||||||
|
background-position: -50px -175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-vk {
|
||||||
|
background-position: -100px -175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-twitter {
|
||||||
|
background-position: -125px -175px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-leanpub {
|
||||||
|
width: 23px;
|
||||||
|
height: 20px;
|
||||||
|
background-position: 0 -200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-hacker-news {
|
||||||
|
width: 17px;
|
||||||
|
height: 20px;
|
||||||
|
background-position: -50px -200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-stumbleupon {
|
||||||
|
width: 21px;
|
||||||
|
height: 20px;
|
||||||
|
background-position: -75px -200px;
|
||||||
|
}
|
@ -1,15 +1,28 @@
|
|||||||
|
@use "../../styles/variables.scss" as *;
|
||||||
|
|
||||||
.backlinks {
|
.backlinks {
|
||||||
position: relative;
|
flex-direction: column;
|
||||||
|
/*&:after {
|
||||||
|
pointer-events: none;
|
||||||
|
content: "";
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
background: linear-gradient(transparent 0px, var(--light));
|
||||||
|
}*/
|
||||||
|
|
||||||
& > h3 {
|
& > h3 {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > ul {
|
& > ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
padding: 0;
|
margin: 0.5rem 0;
|
||||||
margin: 0.5rem 0;
|
|
||||||
|
|
||||||
& > li {
|
& > li {
|
||||||
& > a {
|
& > a {
|
||||||
@ -17,4 +30,14 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
& > .overflow {
|
||||||
|
&:after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
height: auto;
|
||||||
|
@media all and ($desktop) {
|
||||||
|
height: 250px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,28 @@
|
|||||||
@use "../../styles/variables.scss" as *;
|
@use "../../styles/variables.scss" as *;
|
||||||
|
|
||||||
|
.explorer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: hidden;
|
||||||
|
&.desktop-only {
|
||||||
|
@media all and not ($mobile) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*&:after {
|
||||||
|
pointer-events: none;
|
||||||
|
content: "";
|
||||||
|
width: 100%;
|
||||||
|
height: 50px;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
background: linear-gradient(transparent 0px, var(--light));
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
button#explorer {
|
button#explorer {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
@ -44,7 +67,8 @@ button#explorer {
|
|||||||
#explorer-content {
|
#explorer-content {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: none;
|
overflow-y: auto;
|
||||||
|
max-height: 100%;
|
||||||
transition:
|
transition:
|
||||||
max-height 0.35s ease,
|
max-height 0.35s ease,
|
||||||
visibility 0s linear 0s;
|
visibility 0s linear 0s;
|
||||||
@ -52,16 +76,13 @@ button#explorer {
|
|||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
|
max-height: 0;
|
||||||
transition:
|
transition:
|
||||||
max-height 0.35s ease,
|
max-height 0.35s ease,
|
||||||
visibility 0s linear 0.35s;
|
visibility 0s linear 0.35s;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.collapsed > .overflow::after {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
& ul {
|
& ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0.08rem 0;
|
margin: 0.08rem 0;
|
||||||
@ -76,6 +97,9 @@ button#explorer {
|
|||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
> #explorer-ul {
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
|
@ -65,7 +65,7 @@
|
|||||||
height: 80vh;
|
height: 80vh;
|
||||||
width: 80vw;
|
width: 80vw;
|
||||||
|
|
||||||
@media all and (max-width: $fullPageWidth) {
|
@media all and ($desktop) {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ li.section-li {
|
|||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: fit-content(8em) 3fr 1fr;
|
grid-template-columns: fit-content(8em) 3fr 1fr;
|
||||||
|
|
||||||
@media all and (max-width: $mobileBreakpoint) {
|
@media all and ($mobile) {
|
||||||
& > .tags {
|
& > .tags {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@
|
|||||||
opacity 0.3s ease,
|
opacity 0.3s ease,
|
||||||
visibility 0.3s ease;
|
visibility 0.3s ease;
|
||||||
|
|
||||||
@media all and (max-width: $mobileBreakpoint) {
|
@media all and ($mobile) {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,31 @@
|
|||||||
.recent-notes {
|
.recent-notes>h3 {
|
||||||
& > h3 {
|
margin: .5rem 0 0;
|
||||||
margin: 0.5rem 0 0 0;
|
font-size: 1rem
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > ul.recent-ul {
|
|
||||||
list-style: none;
|
|
||||||
margin-top: 1rem;
|
|
||||||
padding-left: 0;
|
|
||||||
|
|
||||||
& > li {
|
|
||||||
margin: 1rem 0;
|
|
||||||
.section > .desc > h3 > a {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.section > .meta {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.recent-notes>ul.recent-ul {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-notes>ul.recent-ul>li {
|
||||||
|
margin: 1rem 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-notes>ul.recent-ul>li .section>.desc>h3>a {
|
||||||
|
background-color: #0000
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-notes>ul.recent-ul>li .section>.meta {
|
||||||
|
opacity: .6;
|
||||||
|
margin: 0 0 .5rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.left .recent-notes:nth-last-child(2) {
|
||||||
|
grid-area: 3/1/3/3
|
||||||
|
}
|
||||||
|
|
||||||
|
.left .recent-notes:last-child {
|
||||||
|
grid-area: 4/1/4/3
|
||||||
|
}
|
@ -3,7 +3,9 @@
|
|||||||
.search {
|
.search {
|
||||||
min-width: fit-content;
|
min-width: fit-content;
|
||||||
max-width: 14rem;
|
max-width: 14rem;
|
||||||
flex-grow: 0.3;
|
@media all and ($mobile) {
|
||||||
|
flex-grow: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
& > .search-button {
|
& > .search-button {
|
||||||
background-color: var(--lightgray);
|
background-color: var(--lightgray);
|
||||||
@ -62,7 +64,7 @@
|
|||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
||||||
@media all and (max-width: $fullPageWidth) {
|
@media all and ($desktop) {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +106,7 @@
|
|||||||
flex: 0 0 min(30%, 450px);
|
flex: 0 0 min(30%, 450px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (min-width: $tabletBreakpoint) {
|
@media all and not ($tablet) {
|
||||||
&[data-preview] {
|
&[data-preview] {
|
||||||
& .result-card > p.preview {
|
& .result-card > p.preview {
|
||||||
display: none;
|
display: none;
|
||||||
@ -130,7 +132,7 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media all and (max-width: $tabletBreakpoint) {
|
@media all and ($tablet) {
|
||||||
& > #preview-container {
|
& > #preview-container {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,12 @@
|
|||||||
|
.toc {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
&.desktop-only {
|
||||||
|
display: flex;
|
||||||
|
max-height: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
button#toc {
|
button#toc {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
@ -28,17 +37,19 @@ button#toc {
|
|||||||
#toc-content {
|
#toc-content {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-height: none;
|
overflow-y: auto;
|
||||||
|
max-height: 100%;
|
||||||
transition:
|
transition:
|
||||||
max-height 0.5s ease,
|
max-height 0.35s ease,
|
||||||
visibility 0s linear 0s;
|
visibility 0s linear 0s;
|
||||||
position: relative;
|
position: relative;
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
|
|
||||||
&.collapsed {
|
&.collapsed {
|
||||||
|
max-height: 0;
|
||||||
transition:
|
transition:
|
||||||
max-height 0.5s ease,
|
max-height 0.35s ease,
|
||||||
visibility 0s linear 0.5s;
|
visibility 0s linear 0.35s;
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,6 +72,10 @@ button#toc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
> ul.overflow {
|
||||||
|
max-height: none;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
@for $i from 0 through 6 {
|
@for $i from 0 through 6 {
|
||||||
& .depth-#{$i} {
|
& .depth-#{$i} {
|
||||||
|
@ -19,6 +19,7 @@ import pt from "./locales/pt-BR"
|
|||||||
import hu from "./locales/hu-HU"
|
import hu from "./locales/hu-HU"
|
||||||
import fa from "./locales/fa-IR"
|
import fa from "./locales/fa-IR"
|
||||||
import pl from "./locales/pl-PL"
|
import pl from "./locales/pl-PL"
|
||||||
|
import cs from "./locales/cs-CZ"
|
||||||
|
|
||||||
export const TRANSLATIONS = {
|
export const TRANSLATIONS = {
|
||||||
"en-US": enUs,
|
"en-US": enUs,
|
||||||
@ -62,6 +63,7 @@ export const TRANSLATIONS = {
|
|||||||
"hu-HU": hu,
|
"hu-HU": hu,
|
||||||
"fa-IR": fa,
|
"fa-IR": fa,
|
||||||
"pl-PL": pl,
|
"pl-PL": pl,
|
||||||
|
"cs-CZ": cs,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const defaultTranslation = "en-US"
|
export const defaultTranslation = "en-US"
|
||||||
|
84
quartz/i18n/locales/cs-CZ.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { Translation } from "./definition"
|
||||||
|
|
||||||
|
export default {
|
||||||
|
propertyDefaults: {
|
||||||
|
title: "Bez názvu",
|
||||||
|
description: "Nebyl uveden žádný popis",
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
callout: {
|
||||||
|
note: "Poznámka",
|
||||||
|
abstract: "Abstract",
|
||||||
|
info: "Info",
|
||||||
|
todo: "Todo",
|
||||||
|
tip: "Tip",
|
||||||
|
success: "Úspěch",
|
||||||
|
question: "Otázka",
|
||||||
|
warning: "Upozornění",
|
||||||
|
failure: "Chyba",
|
||||||
|
danger: "Nebezpečí",
|
||||||
|
bug: "Bug",
|
||||||
|
example: "Příklad",
|
||||||
|
quote: "Citace",
|
||||||
|
},
|
||||||
|
backlinks: {
|
||||||
|
title: "Příchozí odkazy",
|
||||||
|
noBacklinksFound: "Nenalezeny žádné příchozí odkazy",
|
||||||
|
},
|
||||||
|
themeToggle: {
|
||||||
|
lightMode: "Světlý režim",
|
||||||
|
darkMode: "Tmavý režim",
|
||||||
|
},
|
||||||
|
explorer: {
|
||||||
|
title: "Procházet",
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
createdWith: "Vytvořeno pomocí",
|
||||||
|
},
|
||||||
|
graph: {
|
||||||
|
title: "Graf",
|
||||||
|
},
|
||||||
|
recentNotes: {
|
||||||
|
title: "Nejnovější poznámky",
|
||||||
|
seeRemainingMore: ({ remaining }) => `Zobraz ${remaining} dalších →`,
|
||||||
|
},
|
||||||
|
transcludes: {
|
||||||
|
transcludeOf: ({ targetSlug }) => `Zobrazení ${targetSlug}`,
|
||||||
|
linkToOriginal: "Odkaz na původní dokument",
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
title: "Hledat",
|
||||||
|
searchBarPlaceholder: "Hledejte něco",
|
||||||
|
},
|
||||||
|
tableOfContents: {
|
||||||
|
title: "Obsah",
|
||||||
|
},
|
||||||
|
contentMeta: {
|
||||||
|
readingTime: ({ minutes }) => `${minutes} min čtení`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pages: {
|
||||||
|
rss: {
|
||||||
|
recentNotes: "Nejnovější poznámky",
|
||||||
|
lastFewNotes: ({ count }) => `Posledních ${count} poznámek`,
|
||||||
|
},
|
||||||
|
error: {
|
||||||
|
title: "Nenalezeno",
|
||||||
|
notFound: "Tato stránka je buď soukromá, nebo neexistuje.",
|
||||||
|
home: "Návrat na domovskou stránku",
|
||||||
|
},
|
||||||
|
folderContent: {
|
||||||
|
folder: "Složka",
|
||||||
|
itemsUnderFolder: ({ count }) =>
|
||||||
|
count === 1 ? "1 položka v této složce." : `${count} položek v této složce.`,
|
||||||
|
},
|
||||||
|
tagContent: {
|
||||||
|
tag: "Tag",
|
||||||
|
tagIndex: "Rejstřík tagů",
|
||||||
|
itemsUnderTag: ({ count }) =>
|
||||||
|
count === 1 ? "1 položka s tímto tagem." : `${count} položek s tímto tagem.`,
|
||||||
|
showingFirst: ({ count }) => `Zobrazují se první ${count} tagy.`,
|
||||||
|
totalTags: ({ count }) => `Nalezeno celkem ${count} tagů.`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const satisfies Translation
|
@ -45,7 +45,7 @@ export default {
|
|||||||
},
|
},
|
||||||
transcludes: {
|
transcludes: {
|
||||||
transcludeOf: ({ targetSlug }) => `Переход из ${targetSlug}`,
|
transcludeOf: ({ targetSlug }) => `Переход из ${targetSlug}`,
|
||||||
linkToOriginal: "Ссылка на оригинал",
|
linkToOriginal: "Подробнее",
|
||||||
},
|
},
|
||||||
search: {
|
search: {
|
||||||
title: "Поиск",
|
title: "Поиск",
|
||||||
|
@ -147,11 +147,20 @@ function addGlobalPageResources(ctx: BuildCtx, componentResources: ComponentReso
|
|||||||
} else if (cfg.analytics?.provider === "cabin") {
|
} else if (cfg.analytics?.provider === "cabin") {
|
||||||
componentResources.afterDOMLoaded.push(`
|
componentResources.afterDOMLoaded.push(`
|
||||||
const cabinScript = document.createElement("script")
|
const cabinScript = document.createElement("script")
|
||||||
cabinScript.src = "${cfg.analytics.host ?? "https://scripts.cabin.dev"}/cabin.js"
|
cabinScript.src = "${cfg.analytics.host ?? "https://scripts.withcabin.com"}/hello.js"
|
||||||
cabinScript.defer = true
|
cabinScript.defer = true
|
||||||
cabinScript.async = true
|
cabinScript.async = true
|
||||||
document.head.appendChild(cabinScript)
|
document.head.appendChild(cabinScript)
|
||||||
`)
|
`)
|
||||||
|
} else if (cfg.analytics?.provider === "clarity") {
|
||||||
|
componentResources.afterDOMLoaded.push(`
|
||||||
|
const clarityScript = document.createElement("script")
|
||||||
|
clarityScript.innerHTML= \`(function(c,l,a,r,i,t,y){c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
||||||
|
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
||||||
|
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
||||||
|
})(window, document, "clarity", "script", "${cfg.analytics.projectId}");\`
|
||||||
|
document.head.appendChild(clarityScript)
|
||||||
|
`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cfg.enableSPA) {
|
if (cfg.enableSPA) {
|
||||||
|
@ -83,7 +83,6 @@ function generateRSSFeed(cfg: GlobalConfiguration, idx: ContentIndex, limit?: nu
|
|||||||
<description>${!!limit ? i18n(cfg.locale).pages.rss.lastFewNotes({ count: limit }) : i18n(cfg.locale).pages.rss.recentNotes} on ${escapeHTML(
|
<description>${!!limit ? i18n(cfg.locale).pages.rss.lastFewNotes({ count: limit }) : i18n(cfg.locale).pages.rss.recentNotes} on ${escapeHTML(
|
||||||
cfg.pageTitle,
|
cfg.pageTitle,
|
||||||
)}</description>
|
)}</description>
|
||||||
<generator>Quartz -- quartz.jzhao.xyz</generator>
|
|
||||||
${items}
|
${items}
|
||||||
</channel>
|
</channel>
|
||||||
</rss>`
|
</rss>`
|
||||||
|
@ -21,7 +21,7 @@ export const Citations: QuartzTransformerPlugin<Partial<Options>> = (userOpts) =
|
|||||||
const opts = { ...defaultOptions, ...userOpts }
|
const opts = { ...defaultOptions, ...userOpts }
|
||||||
return {
|
return {
|
||||||
name: "Citations",
|
name: "Citations",
|
||||||
htmlPlugins() {
|
htmlPlugins(ctx) {
|
||||||
const plugins: PluggableList = []
|
const plugins: PluggableList = []
|
||||||
|
|
||||||
// Add rehype-citation to the list of plugins
|
// Add rehype-citation to the list of plugins
|
||||||
@ -31,6 +31,8 @@ export const Citations: QuartzTransformerPlugin<Partial<Options>> = (userOpts) =
|
|||||||
bibliography: opts.bibliographyFile,
|
bibliography: opts.bibliographyFile,
|
||||||
suppressBibliography: opts.suppressBibliography,
|
suppressBibliography: opts.suppressBibliography,
|
||||||
linkCitations: opts.linkCitations,
|
linkCitations: opts.linkCitations,
|
||||||
|
csl: opts.csl,
|
||||||
|
lang: ctx.cfg.configuration.locale ?? "en-US",
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ declare module "vfile" {
|
|||||||
draft: boolean
|
draft: boolean
|
||||||
lang: string
|
lang: string
|
||||||
enableToc: string
|
enableToc: string
|
||||||
|
enableComments: string
|
||||||
cssclasses: string[]
|
cssclasses: string[]
|
||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
@ -10,3 +10,4 @@ export { OxHugoFlavouredMarkdown } from "./oxhugofm"
|
|||||||
export { SyntaxHighlighting } from "./syntax"
|
export { SyntaxHighlighting } from "./syntax"
|
||||||
export { TableOfContents } from "./toc"
|
export { TableOfContents } from "./toc"
|
||||||
export { HardLineBreaks } from "./linebreaks"
|
export { HardLineBreaks } from "./linebreaks"
|
||||||
|
export { RoamFlavoredMarkdown } from "./roam"
|
||||||
|
@ -58,18 +58,19 @@ export const CreatedModifiedDate: QuartzTransformerPlugin<Partial<Options>> = (u
|
|||||||
// Get a reference to the main git repo.
|
// Get a reference to the main git repo.
|
||||||
// It's either the same as the workdir,
|
// It's either the same as the workdir,
|
||||||
// or 1+ level higher in case of a submodule/subtree setup
|
// or 1+ level higher in case of a submodule/subtree setup
|
||||||
repo = Repository.discover(file.cwd)
|
try {
|
||||||
|
repo = await Repository.discover(path.join(file.cwd, 'content'), { search: true });
|
||||||
|
console.log(chalk.green(`Git repository found at: ${repo.path()}`));
|
||||||
|
} catch (error) {
|
||||||
|
console.log(chalk.red(`Error discovering Git repository: ${error.message}`));
|
||||||
|
repo = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
modified ||= await repo.getFileLatestModifiedDateAsync(file.data.filePath!)
|
modified ||= await repo.getFileLatestModifiedDateAsync(file.data.filePath!.replace(/^content\//, ''));
|
||||||
} catch {
|
} catch (error) {
|
||||||
console.log(
|
console.log(chalk.red(`Error retrieving latest modified date for ${file.data.filePath}: ${error.message}`));
|
||||||
chalk.yellow(
|
|
||||||
`\nWarning: ${file.data
|
|
||||||
.filePath!} isn't yet tracked by git, last modification date is not available for this file`,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,28 +60,6 @@ export const CrawlLinks: QuartzTransformerPlugin<Partial<Options>> = (userOpts)
|
|||||||
const isExternal = isAbsoluteUrl(dest)
|
const isExternal = isAbsoluteUrl(dest)
|
||||||
classes.push(isExternal ? "external" : "internal")
|
classes.push(isExternal ? "external" : "internal")
|
||||||
|
|
||||||
if (isExternal && opts.externalLinkIcon) {
|
|
||||||
node.children.push({
|
|
||||||
type: "element",
|
|
||||||
tagName: "svg",
|
|
||||||
properties: {
|
|
||||||
"aria-hidden": "true",
|
|
||||||
class: "external-icon",
|
|
||||||
viewBox: "0 0 512 512",
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
type: "element",
|
|
||||||
tagName: "path",
|
|
||||||
properties: {
|
|
||||||
d: "M320 0H288V64h32 82.7L201.4 265.4 178.7 288 224 333.3l22.6-22.6L448 109.3V192v32h64V192 32 0H480 320zM32 32H0V64 480v32H32 456h32V480 352 320H424v32 96H64V96h96 32V32H160 32z",
|
|
||||||
},
|
|
||||||
children: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the link has alias text
|
// Check if the link has alias text
|
||||||
if (
|
if (
|
||||||
node.children.length === 1 &&
|
node.children.length === 1 &&
|
||||||
|
@ -119,7 +119,7 @@ export const tableWikilinkRegex = new RegExp(/(!?\[\[[^\]]*?\]\])/g)
|
|||||||
const highlightRegex = new RegExp(/==([^=]+)==/g)
|
const highlightRegex = new RegExp(/==([^=]+)==/g)
|
||||||
const commentRegex = new RegExp(/%%[\s\S]*?%%/g)
|
const commentRegex = new RegExp(/%%[\s\S]*?%%/g)
|
||||||
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
// from https://github.com/escwxyz/remark-obsidian-callout/blob/main/src/index.ts
|
||||||
const calloutRegex = new RegExp(/^\[\!(\w+)\|?(.+?)?\]([+-]?)/)
|
const calloutRegex = new RegExp(/^\[\!([\w-]+)\|?(.+?)?\]([+-]?)/)
|
||||||
const calloutLineRegex = new RegExp(/^> *\[\!\w+\|?.*?\][+-]?.*$/gm)
|
const calloutLineRegex = new RegExp(/^> *\[\!\w+\|?.*?\][+-]?.*$/gm)
|
||||||
// (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line
|
// (?:^| ) -> non-capturing group, tag should start be separated by a space or be the start of the line
|
||||||
// #(...) -> capturing group, tag itself must start with #
|
// #(...) -> capturing group, tag itself must start with #
|
||||||
@ -128,7 +128,7 @@ const calloutLineRegex = new RegExp(/^> *\[\!\w+\|?.*?\][+-]?.*$/gm)
|
|||||||
const tagRegex = new RegExp(
|
const tagRegex = new RegExp(
|
||||||
/(?:^| )#((?:[-_\p{L}\p{Emoji}\p{M}\d])+(?:\/[-_\p{L}\p{Emoji}\p{M}\d]+)*)/gu,
|
/(?:^| )#((?:[-_\p{L}\p{Emoji}\p{M}\d])+(?:\/[-_\p{L}\p{Emoji}\p{M}\d]+)*)/gu,
|
||||||
)
|
)
|
||||||
const blockReferenceRegex = new RegExp(/\^([-_A-Za-z0-9]+)$/g)
|
const blockReferenceRegex = new RegExp(/\^([-_A-Za-z0-9\u0400-\u04FF]+)$/g);
|
||||||
const ytLinkRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
|
const ytLinkRegex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/
|
||||||
const ytPlaylistLinkRegex = /[?&]list=([^#?&]*)/
|
const ytPlaylistLinkRegex = /[?&]list=([^#?&]*)/
|
||||||
const videoExtensionRegex = new RegExp(/\.(mp4|webm|ogg|avi|mov|flv|wmv|mkv|mpg|mpeg|3gp|m4v)$/)
|
const videoExtensionRegex = new RegExp(/\.(mp4|webm|ogg|avi|mov|flv|wmv|mkv|mpg|mpeg|3gp|m4v)$/)
|
||||||
@ -324,8 +324,8 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
|
|||||||
replacements.push([
|
replacements.push([
|
||||||
tagRegex,
|
tagRegex,
|
||||||
(_value: string, tag: string) => {
|
(_value: string, tag: string) => {
|
||||||
// Check if the tag only includes numbers
|
// Check if the tag only includes numbers and slashes
|
||||||
if (/^\d+$/.test(tag)) {
|
if (/^[\/\d]+$/.test(tag)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -430,7 +430,9 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
type: "text",
|
type: "text",
|
||||||
value: useDefaultTitle ? capitalize(typeString) : titleContent + " ",
|
value: useDefaultTitle
|
||||||
|
? capitalize(typeString).replace(/-/g, " ")
|
||||||
|
: titleContent + " ",
|
||||||
},
|
},
|
||||||
...restOfTitle,
|
...restOfTitle,
|
||||||
],
|
],
|
||||||
|
224
quartz/plugins/transformers/roam.ts
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
import { QuartzTransformerPlugin } from "../types"
|
||||||
|
import { PluggableList } from "unified"
|
||||||
|
import { SKIP, visit } from "unist-util-visit"
|
||||||
|
import { ReplaceFunction, findAndReplace as mdastFindReplace } from "mdast-util-find-and-replace"
|
||||||
|
import { Root, Html, Paragraph, Text, Link, Parent } from "mdast"
|
||||||
|
import { Node } from "unist"
|
||||||
|
import { VFile } from "vfile"
|
||||||
|
import { BuildVisitor } from "unist-util-visit"
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
orComponent: boolean
|
||||||
|
TODOComponent: boolean
|
||||||
|
DONEComponent: boolean
|
||||||
|
videoComponent: boolean
|
||||||
|
audioComponent: boolean
|
||||||
|
pdfComponent: boolean
|
||||||
|
blockquoteComponent: boolean
|
||||||
|
tableComponent: boolean
|
||||||
|
attributeComponent: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultOptions: Options = {
|
||||||
|
orComponent: true,
|
||||||
|
TODOComponent: true,
|
||||||
|
DONEComponent: true,
|
||||||
|
videoComponent: true,
|
||||||
|
audioComponent: true,
|
||||||
|
pdfComponent: true,
|
||||||
|
blockquoteComponent: true,
|
||||||
|
tableComponent: true,
|
||||||
|
attributeComponent: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
const orRegex = new RegExp(/{{or:(.*?)}}/, "g")
|
||||||
|
const TODORegex = new RegExp(/{{.*?\bTODO\b.*?}}/, "g")
|
||||||
|
const DONERegex = new RegExp(/{{.*?\bDONE\b.*?}}/, "g")
|
||||||
|
const videoRegex = new RegExp(/{{.*?\[\[video\]\].*?\:(.*?)}}/, "g")
|
||||||
|
const youtubeRegex = new RegExp(
|
||||||
|
/{{.*?\[\[video\]\].*?(https?:\/\/(?:www\.)?youtu(?:be\.com\/watch\?v=|\.be\/)([\w\-\_]*)(&(amp;)?[\w\?=]*)?)}}/,
|
||||||
|
"g",
|
||||||
|
)
|
||||||
|
|
||||||
|
// const multimediaRegex = new RegExp(/{{.*?\b(video|audio)\b.*?\:(.*?)}}/, "g")
|
||||||
|
|
||||||
|
const audioRegex = new RegExp(/{{.*?\[\[audio\]\].*?\:(.*?)}}/, "g")
|
||||||
|
const pdfRegex = new RegExp(/{{.*?\[\[pdf\]\].*?\:(.*?)}}/, "g")
|
||||||
|
const blockquoteRegex = new RegExp(/(\[\[>\]\])\s*(.*)/, "g")
|
||||||
|
const roamHighlightRegex = new RegExp(/\^\^(.+)\^\^/, "g")
|
||||||
|
const roamItalicRegex = new RegExp(/__(.+)__/, "g")
|
||||||
|
const tableRegex = new RegExp(/- {{.*?\btable\b.*?}}/, "g") /* TODO */
|
||||||
|
const attributeRegex = new RegExp(/\b\w+(?:\s+\w+)*::/, "g") /* TODO */
|
||||||
|
|
||||||
|
function isSpecialEmbed(node: Paragraph): boolean {
|
||||||
|
if (node.children.length !== 2) return false
|
||||||
|
|
||||||
|
const [textNode, linkNode] = node.children
|
||||||
|
return (
|
||||||
|
textNode.type === "text" &&
|
||||||
|
textNode.value.startsWith("{{[[") &&
|
||||||
|
linkNode.type === "link" &&
|
||||||
|
linkNode.children[0].type === "text" &&
|
||||||
|
linkNode.children[0].value.endsWith("}}")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformSpecialEmbed(node: Paragraph, opts: Options): Html | null {
|
||||||
|
const [textNode, linkNode] = node.children as [Text, Link]
|
||||||
|
const embedType = textNode.value.match(/\{\{\[\[(.*?)\]\]:/)?.[1]?.toLowerCase()
|
||||||
|
const url = linkNode.url.slice(0, -2) // Remove the trailing '}}'
|
||||||
|
|
||||||
|
switch (embedType) {
|
||||||
|
case "audio":
|
||||||
|
return opts.audioComponent
|
||||||
|
? {
|
||||||
|
type: "html",
|
||||||
|
value: `<audio controls>
|
||||||
|
<source src="${url}" type="audio/mpeg">
|
||||||
|
<source src="${url}" type="audio/ogg">
|
||||||
|
Your browser does not support the audio tag.
|
||||||
|
</audio>`,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
case "video":
|
||||||
|
if (!opts.videoComponent) return null
|
||||||
|
// Check if it's a YouTube video
|
||||||
|
const youtubeMatch = url.match(
|
||||||
|
/(?:https?:\/\/)?(?:www\.)?(?:youtube\.com|youtu\.be)\/(?:watch\?v=)?(.+)/,
|
||||||
|
)
|
||||||
|
if (youtubeMatch) {
|
||||||
|
const videoId = youtubeMatch[1].split("&")[0] // Remove additional parameters
|
||||||
|
const playlistMatch = url.match(/[?&]list=([^#\&\?]*)/)
|
||||||
|
const playlistId = playlistMatch ? playlistMatch[1] : null
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "html",
|
||||||
|
value: `<iframe
|
||||||
|
class="external-embed youtube"
|
||||||
|
width="600px"
|
||||||
|
height="350px"
|
||||||
|
src="https://www.youtube.com/embed/${videoId}${playlistId ? `?list=${playlistId}` : ""}"
|
||||||
|
frameborder="0"
|
||||||
|
allow="fullscreen"
|
||||||
|
></iframe>`,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
type: "html",
|
||||||
|
value: `<video controls>
|
||||||
|
<source src="${url}" type="video/mp4">
|
||||||
|
<source src="${url}" type="video/webm">
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "pdf":
|
||||||
|
return opts.pdfComponent
|
||||||
|
? {
|
||||||
|
type: "html",
|
||||||
|
value: `<embed src="${url}" type="application/pdf" width="100%" height="600px" />`,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RoamFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options> | undefined> = (
|
||||||
|
userOpts,
|
||||||
|
) => {
|
||||||
|
const opts = { ...defaultOptions, ...userOpts }
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: "RoamFlavoredMarkdown",
|
||||||
|
markdownPlugins() {
|
||||||
|
const plugins: PluggableList = []
|
||||||
|
|
||||||
|
plugins.push(() => {
|
||||||
|
return (tree: Root, file: VFile) => {
|
||||||
|
const replacements: [RegExp, ReplaceFunction][] = []
|
||||||
|
|
||||||
|
// Handle special embeds (audio, video, PDF)
|
||||||
|
if (opts.audioComponent || opts.videoComponent || opts.pdfComponent) {
|
||||||
|
visit(tree, "paragraph", ((node: Paragraph, index: number, parent: Parent | null) => {
|
||||||
|
if (isSpecialEmbed(node)) {
|
||||||
|
const transformedNode = transformSpecialEmbed(node, opts)
|
||||||
|
if (transformedNode && parent) {
|
||||||
|
parent.children[index] = transformedNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) as BuildVisitor<Root, "paragraph">)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roam italic syntax
|
||||||
|
replacements.push([
|
||||||
|
roamItalicRegex,
|
||||||
|
(_value: string, match: string) => ({
|
||||||
|
type: "emphasis",
|
||||||
|
children: [{ type: "text", value: match }],
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
// Roam highlight syntax
|
||||||
|
replacements.push([
|
||||||
|
roamHighlightRegex,
|
||||||
|
(_value: string, inner: string) => ({
|
||||||
|
type: "html",
|
||||||
|
value: `<span class="text-highlight">${inner}</span>`,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
if (opts.orComponent) {
|
||||||
|
replacements.push([
|
||||||
|
orRegex,
|
||||||
|
(match: string) => {
|
||||||
|
const matchResult = match.match(/{{or:(.*?)}}/)
|
||||||
|
if (matchResult === null) {
|
||||||
|
return { type: "html", value: "" }
|
||||||
|
}
|
||||||
|
const optionsString: string = matchResult[1]
|
||||||
|
const options: string[] = optionsString.split("|")
|
||||||
|
const selectHtml: string = `<select>${options.map((option: string) => `<option value="${option}">${option}</option>`).join("")}</select>`
|
||||||
|
return { type: "html", value: selectHtml }
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.TODOComponent) {
|
||||||
|
replacements.push([
|
||||||
|
TODORegex,
|
||||||
|
() => ({
|
||||||
|
type: "html",
|
||||||
|
value: `<input type="checkbox" disabled>`,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.DONEComponent) {
|
||||||
|
replacements.push([
|
||||||
|
DONERegex,
|
||||||
|
() => ({
|
||||||
|
type: "html",
|
||||||
|
value: `<input type="checkbox" checked disabled>`,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.blockquoteComponent) {
|
||||||
|
replacements.push([
|
||||||
|
blockquoteRegex,
|
||||||
|
(_match: string, _marker: string, content: string) => ({
|
||||||
|
type: "html",
|
||||||
|
value: `<blockquote>${content.trim()}</blockquote>`,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
mdastFindReplace(tree, replacements)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return plugins
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 38 KiB |
BIN
quartz/static/og-image.webp
Normal file
After Width: | Height: | Size: 53 KiB |
455
quartz/static/share.js
Normal file
1
quartz/static/social-sprite-color.svg
Normal file
After Width: | Height: | Size: 66 KiB |
1
quartz/static/social-sprite.svg
Normal file
After Width: | Height: | Size: 66 KiB |
@ -12,7 +12,6 @@ html {
|
|||||||
body,
|
body,
|
||||||
section {
|
section {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
max-width: 100%;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: var(--light);
|
background-color: var(--light);
|
||||||
font-family: var(--bodyFont);
|
font-family: var(--bodyFont);
|
||||||
@ -97,37 +96,25 @@ a {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.external .external-icon {
|
|
||||||
height: 1ex;
|
|
||||||
margin: 0 0.15em;
|
|
||||||
|
|
||||||
> path {
|
|
||||||
fill: var(--dark);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.desktop-only {
|
.desktop-only {
|
||||||
display: initial;
|
display: initial;
|
||||||
@media all and (max-width: $fullPageWidth) {
|
@media all and ($mobile) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-only {
|
.mobile-only {
|
||||||
display: none;
|
display: none;
|
||||||
@media all and (max-width: $fullPageWidth) {
|
@media all and ($mobile) {
|
||||||
display: initial;
|
display: initial;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page {
|
.page {
|
||||||
@media all and (max-width: $fullPageWidth) {
|
max-width: calc(#{map-get($breakpoints, desktop)} + 300px);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 0 1rem;
|
|
||||||
max-width: $pageWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
& article {
|
& article {
|
||||||
& > h1 {
|
& > h1 {
|
||||||
font-size: 2rem;
|
font-size: 2rem;
|
||||||
@ -155,79 +142,119 @@ a {
|
|||||||
}
|
}
|
||||||
|
|
||||||
& > #quartz-body {
|
& > #quartz-body {
|
||||||
width: 100%;
|
display: grid;
|
||||||
display: flex;
|
grid-template-columns: #{map-get($desktopGrid, templateColumns)};
|
||||||
@media all and (max-width: $fullPageWidth) {
|
grid-template-rows: #{map-get($desktopGrid, templateRows)};
|
||||||
flex-direction: column;
|
column-gap: #{map-get($desktopGrid, columnGap)};
|
||||||
|
row-gap: #{map-get($desktopGrid, rowGap)};
|
||||||
|
grid-template-areas: #{map-get($desktopGrid, templateAreas)};
|
||||||
|
@media all and ($desktop) {
|
||||||
|
grid-template-columns: #{map-get($tabletGrid, templateColumns)};
|
||||||
|
grid-template-rows: #{map-get($tabletGrid, templateRows)};
|
||||||
|
column-gap: #{map-get($tabletGrid, columnGap)};
|
||||||
|
row-gap: #{map-get($tabletGrid, rowGap)};
|
||||||
|
grid-template-areas: #{map-get($tabletGrid, templateAreas)};
|
||||||
|
}
|
||||||
|
@media all and ($mobile) {
|
||||||
|
grid-template-columns: #{map-get($mobileGrid, templateColumns)};
|
||||||
|
grid-template-rows: #{map-get($mobileGrid, templateRows)};
|
||||||
|
column-gap: #{map-get($mobileGrid, columnGap)};
|
||||||
|
row-gap: #{map-get($mobileGrid, rowGap)};
|
||||||
|
grid-template-areas: #{map-get($mobileGrid, templateAreas)};
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and ($desktop) {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
@media all and ($mobile) {
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .sidebar {
|
& .sidebar {
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2rem;
|
gap: 2rem;
|
||||||
top: 0;
|
top: 0;
|
||||||
width: $sidePanelWidth;
|
|
||||||
margin-top: $topSpacing;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 0 4rem;
|
padding: $topSpacing 2rem 2rem 2rem;
|
||||||
position: fixed;
|
display: flex;
|
||||||
@media all and (max-width: $fullPageWidth) {
|
height: 100vh;
|
||||||
position: initial;
|
position: sticky;
|
||||||
flex-direction: row;
|
|
||||||
padding: 0;
|
|
||||||
width: initial;
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
& .sidebar.left {
|
& .sidebar.left {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
left: calc(calc(100vw - $pageWidth) / 2 - $sidePanelWidth);
|
grid-area: grid-sidebar-left;
|
||||||
@media all and (max-width: $fullPageWidth) {
|
flex-direction: column;
|
||||||
|
@media all and ($mobile) {
|
||||||
gap: 0;
|
gap: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
position: initial;
|
||||||
|
display: flex;
|
||||||
|
height: unset;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0;
|
||||||
|
padding-top: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
& .sidebar.right {
|
& .sidebar.right {
|
||||||
right: calc(calc(100vw - $pageWidth) / 2 - $sidePanelWidth);
|
grid-area: grid-sidebar-right;
|
||||||
flex-wrap: wrap;
|
margin-right: 0;
|
||||||
& > * {
|
flex-direction: column;
|
||||||
@media all and (max-width: $fullPageWidth) {
|
@media all and ($mobile) {
|
||||||
|
margin-left: inherit;
|
||||||
|
margin-right: inherit;
|
||||||
|
}
|
||||||
|
@media all and ($desktop) {
|
||||||
|
position: initial;
|
||||||
|
height: unset;
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: row;
|
||||||
|
padding: 0;
|
||||||
|
& > * {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 140px;
|
}
|
||||||
|
& > .toc {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
& .page-header,
|
||||||
|
& .page-footer {
|
||||||
& .page-header,
|
margin-top: 1rem;
|
||||||
& .page-footer {
|
|
||||||
width: $pageWidth;
|
|
||||||
margin-top: 1rem;
|
|
||||||
|
|
||||||
@media all and (max-width: $fullPageWidth) {
|
|
||||||
width: initial;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
& .page-header {
|
& .page-header {
|
||||||
margin: $topSpacing auto 0 auto;
|
grid-area: grid-header;
|
||||||
@media all and (max-width: $fullPageWidth) {
|
margin: $topSpacing 0 0 0;
|
||||||
margin-top: 2rem;
|
@media all and ($mobile) {
|
||||||
|
margin-top: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
& .center,
|
& .center > article {
|
||||||
& footer {
|
grid-area: grid-center;
|
||||||
margin-left: auto;
|
}
|
||||||
margin-right: auto;
|
|
||||||
width: $pageWidth;
|
& footer {
|
||||||
@media all and (max-width: $fullPageWidth) {
|
grid-area: grid-footer;
|
||||||
width: initial;
|
}
|
||||||
|
|
||||||
|
& .center,
|
||||||
|
& footer {
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 100%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
@media all and ($desktop) {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
@media all and ($mobile) {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& footer {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
margin-right: 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -383,7 +410,7 @@ pre {
|
|||||||
counter-increment: line 0;
|
counter-increment: line 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
padding: 0.5rem 0;
|
padding: 0.5rem 0;
|
||||||
overflow-x: scroll;
|
overflow-x: auto;
|
||||||
|
|
||||||
& [data-highlighted-chars] {
|
& [data-highlighted-chars] {
|
||||||
background-color: var(--highlight);
|
background-color: var(--highlight);
|
||||||
@ -502,12 +529,14 @@ video {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div:has(> .overflow) {
|
div:has(> .overflow) {
|
||||||
position: relative;
|
display: flex;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
ul.overflow,
|
ul.overflow,
|
||||||
ol.overflow {
|
ol.overflow {
|
||||||
max-height: 400;
|
max-height: 100%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
// clearfix
|
// clearfix
|
||||||
@ -517,8 +546,7 @@ ol.overflow {
|
|||||||
& > li:last-of-type {
|
& > li:last-of-type {
|
||||||
margin-bottom: 30px;
|
margin-bottom: 30px;
|
||||||
}
|
}
|
||||||
|
/*&:after {
|
||||||
&:after {
|
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
content: "";
|
content: "";
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -529,7 +557,7 @@ ol.overflow {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: opacity 0.3s ease;
|
transition: opacity 0.3s ease;
|
||||||
background: linear-gradient(transparent 0px, var(--light));
|
background: linear-gradient(transparent 0px, var(--light));
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.transclude {
|
.transclude {
|
||||||
|
@ -1,3 +1,232 @@
|
|||||||
@use "./base.scss";
|
@use "./base.scss";
|
||||||
|
|
||||||
// put your custom CSS here!
|
a[href^=http]:not([href*=garden\.struchkov\.dev]):not([href*=t\.me\/struchkov_dev]):after {
|
||||||
|
content: " ↗"
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.external {
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 1.3px dashed;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.internal {
|
||||||
|
border-radius: .1875rem;
|
||||||
|
padding: 0 .3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recent-notes>ul.recent-ul>li .section>.desc>span>a {
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search>#search-container {
|
||||||
|
background-color: #23252f33;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.backlinks>ul {
|
||||||
|
list-style: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.github-source {
|
||||||
|
a {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.section-li>.section .meta {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
margin: 0.5rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-highlight {
|
||||||
|
margin: -3px -3px -6px;
|
||||||
|
padding: 3px 3px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-top: 6rem;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
margin-top: 4rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backlinks {
|
||||||
|
h3 {
|
||||||
|
margin-top: 4rem;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-top: 2rem;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
article strong {
|
||||||
|
color: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre)>code {
|
||||||
|
background: #e5eff5;
|
||||||
|
border: 1px solid var(--secondary);
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #903;
|
||||||
|
font-size: .85em;
|
||||||
|
padding: .24rem .4rem .19rem;
|
||||||
|
text-shadow: none;
|
||||||
|
vertical-align: 0;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popover-hint {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
border-color: var(--secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
align-items: flex-start;
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 1rem;
|
||||||
|
position: relative
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice.notice-telegram {
|
||||||
|
background-color: #5381bd
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice.notice-twitter {
|
||||||
|
background-color: #1da1f2
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice.notice-comments {
|
||||||
|
background-color: #2c678d
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice.notice-donate {
|
||||||
|
background-color: #ee5162;
|
||||||
|
background-color: var(--notice-donate)
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice__head {
|
||||||
|
align-items: center;
|
||||||
|
border: 1px dashed rgba(206,226,241,.85);
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
padding: .5rem 2rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice__head-icon {
|
||||||
|
color: #fff;
|
||||||
|
width: 1.25rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice__head-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: .9375rem;
|
||||||
|
letter-spacing: .0625rem;
|
||||||
|
margin: 0 0 0 1rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.kg-width-wide .notice__head-text {
|
||||||
|
font-size: 1.1rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice__tail {
|
||||||
|
margin: .5rem 0 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice__tail-left {
|
||||||
|
margin: 0 .5rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice__tail-text {
|
||||||
|
color: #fff;
|
||||||
|
font-size: .9375rem;
|
||||||
|
letter-spacing: .03125rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.kg-width-wide .notice__tail-text {
|
||||||
|
font-size: 1.3rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice__skin {
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
top: 1rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice__skin-icon {
|
||||||
|
color: rgba(206,226,241,.15);
|
||||||
|
width: 10rem
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 36em) {
|
||||||
|
.notice {
|
||||||
|
align-items:center;
|
||||||
|
flex-direction: row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 48em) {
|
||||||
|
.notice {
|
||||||
|
padding:1rem 3rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.kg-width-wide .notice {
|
||||||
|
padding: 2rem 3rem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 36em) {
|
||||||
|
.notice__tail {
|
||||||
|
margin:0 0 0 1.25rem
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice__tail-left {
|
||||||
|
margin: 0 1.25rem 0 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice__tail-text {
|
||||||
|
font-size: 1.0625rem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 48em) {
|
||||||
|
.notice__skin {
|
||||||
|
right:3rem
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.notice__tail-left {
|
||||||
|
display:none
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 980px) {
|
||||||
|
.footer-notice .notice {
|
||||||
|
border-radius:0
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,56 @@
|
|||||||
$pageWidth: 750px;
|
/**
|
||||||
$mobileBreakpoint: 600px;
|
* Layout breakpoints
|
||||||
$tabletBreakpoint: 1000px;
|
* $mobile: screen width below this value will use mobile styles
|
||||||
$sidePanelWidth: 380px;
|
* $desktop: screen width above this value will use desktop styles
|
||||||
|
* Screen width between $mobile and $desktop width will use the tablet layout.
|
||||||
|
* assuming mobile < desktop
|
||||||
|
*/
|
||||||
|
$breakpoints: (
|
||||||
|
mobile: 800px,
|
||||||
|
desktop: 1200px,
|
||||||
|
);
|
||||||
|
|
||||||
|
$mobile: "(max-width: #{map-get($breakpoints, mobile)})";
|
||||||
|
$tablet: "(min-width: #{map-get($breakpoints, mobile)}) and (max-width: #{map-get($breakpoints, desktop)})";
|
||||||
|
$desktop: "(max-width: #{map-get($breakpoints, desktop)})";
|
||||||
|
|
||||||
|
$pageWidth: #{map-get($breakpoints, mobile)};
|
||||||
|
$sidePanelWidth: 320px; //380px;
|
||||||
$topSpacing: 6rem;
|
$topSpacing: 6rem;
|
||||||
$fullPageWidth: $pageWidth + 2 * $sidePanelWidth;
|
|
||||||
$boldWeight: 700;
|
$boldWeight: 700;
|
||||||
$semiBoldWeight: 600;
|
$semiBoldWeight: 600;
|
||||||
$normalWeight: 400;
|
$normalWeight: 400;
|
||||||
|
|
||||||
|
$mobileGrid: (
|
||||||
|
templateRows: "auto auto auto auto auto",
|
||||||
|
templateColumns: "auto",
|
||||||
|
rowGap: "5px",
|
||||||
|
columnGap: "5px",
|
||||||
|
templateAreas:
|
||||||
|
'"grid-sidebar-left"\
|
||||||
|
"grid-header"\
|
||||||
|
"grid-center"\
|
||||||
|
"grid-sidebar-right"\
|
||||||
|
"grid-footer"',
|
||||||
|
);
|
||||||
|
$tabletGrid: (
|
||||||
|
templateRows: "auto auto auto auto",
|
||||||
|
templateColumns: "#{$sidePanelWidth} auto",
|
||||||
|
rowGap: "5px",
|
||||||
|
columnGap: "5px",
|
||||||
|
templateAreas:
|
||||||
|
'"grid-sidebar-left grid-header"\
|
||||||
|
"grid-sidebar-left grid-center"\
|
||||||
|
"grid-sidebar-left grid-sidebar-right"\
|
||||||
|
"grid-sidebar-left grid-footer"',
|
||||||
|
);
|
||||||
|
$desktopGrid: (
|
||||||
|
templateRows: "auto auto auto",
|
||||||
|
templateColumns: "#{$sidePanelWidth} auto #{$sidePanelWidth}",
|
||||||
|
rowGap: "5px",
|
||||||
|
columnGap: "5px",
|
||||||
|
templateAreas:
|
||||||
|
'"grid-sidebar-left grid-header grid-sidebar-right"\
|
||||||
|
"grid-sidebar-left grid-center grid-sidebar-right"\
|
||||||
|
"grid-sidebar-left grid-footer grid-sidebar-right"',
|
||||||
|
);
|
||||||
|
@ -8,6 +8,7 @@ export interface ColorScheme {
|
|||||||
tertiary: string
|
tertiary: string
|
||||||
highlight: string
|
highlight: string
|
||||||
textHighlight: string
|
textHighlight: string
|
||||||
|
shareIcon: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Colors {
|
interface Colors {
|
||||||
@ -51,6 +52,7 @@ ${stylesheet.join("\n\n")}
|
|||||||
--tertiary: ${theme.colors.lightMode.tertiary};
|
--tertiary: ${theme.colors.lightMode.tertiary};
|
||||||
--highlight: ${theme.colors.lightMode.highlight};
|
--highlight: ${theme.colors.lightMode.highlight};
|
||||||
--textHighlight: ${theme.colors.lightMode.textHighlight};
|
--textHighlight: ${theme.colors.lightMode.textHighlight};
|
||||||
|
--sprite-share-icon-color: ${theme.colors.lightMode.shareIcon};
|
||||||
|
|
||||||
--headerFont: "${theme.typography.header}", ${DEFAULT_SANS_SERIF};
|
--headerFont: "${theme.typography.header}", ${DEFAULT_SANS_SERIF};
|
||||||
--bodyFont: "${theme.typography.body}", ${DEFAULT_SANS_SERIF};
|
--bodyFont: "${theme.typography.body}", ${DEFAULT_SANS_SERIF};
|
||||||
@ -67,6 +69,7 @@ ${stylesheet.join("\n\n")}
|
|||||||
--tertiary: ${theme.colors.darkMode.tertiary};
|
--tertiary: ${theme.colors.darkMode.tertiary};
|
||||||
--highlight: ${theme.colors.darkMode.highlight};
|
--highlight: ${theme.colors.darkMode.highlight};
|
||||||
--textHighlight: ${theme.colors.darkMode.textHighlight};
|
--textHighlight: ${theme.colors.darkMode.textHighlight};
|
||||||
|
--sprite-share-icon: ${theme.colors.darkMode.shareIcon};
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
34
zip_image.sh
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
file=comp.flag
|
||||||
|
|
||||||
|
if [ -f "$file" ]; then
|
||||||
|
option="-newer $file"
|
||||||
|
fi
|
||||||
|
|
||||||
|
find ./images/ -type f -not -path "./images/comp/*" ! -name "*-no-comp.*" $option -iname "*.png" -exec sh -c '
|
||||||
|
png_file="${1/\/images\//\/images\/comp\/}"
|
||||||
|
png_dir="$(dirname "$png_file")"
|
||||||
|
mkdir -p "$png_dir"
|
||||||
|
cp "$1" "${png_file}"
|
||||||
|
optipng -o7 "${png_file}"
|
||||||
|
advpng -z4 "${png_file}"
|
||||||
|
pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time -ow "${png_file}"
|
||||||
|
' _ {} \;
|
||||||
|
|
||||||
|
find ./images/ -type f -not -path "./images/comp/*" ! -name "*-no-comp.*" $option -iregex '.*\.\(jpg\|jpeg\)' -exec sh -c '
|
||||||
|
jpg_file="${1/\/images\//\/images\/comp\/}"
|
||||||
|
jpg_dir="$(dirname "$jpg_file")"
|
||||||
|
mkdir -p "$jpg_dir"
|
||||||
|
cp "$1" "${jpg_file}"
|
||||||
|
jpegoptim --all-progressive "${jpg_file}"
|
||||||
|
' _ {} \;
|
||||||
|
|
||||||
|
find ./images/comp -type f -iregex '.*\.\(jpg\|jpeg\|png\)' -not -iregex '.*no-comp\.\(jpg\|jpeg\|png\)' $option -exec sh -c '
|
||||||
|
webp_file="${1/\/images\/comp\//\/images\/webp\/}"
|
||||||
|
webp_dir="$(dirname "$webp_file")"
|
||||||
|
mkdir -p "$webp_dir"
|
||||||
|
cwebp -mt -af -progress -m 6 -q 75 -pass 10 "$1" -o "${webp_file%.*}.webp"
|
||||||
|
' _ {} \;
|
||||||
|
|
||||||
|
touch $file
|
||||||
|
echo "$(date)" > $file
|