Add new article
This commit is contained in:
78
package-lock.json
generated
78
package-lock.json
generated
@@ -1140,6 +1140,45 @@
|
|||||||
"unist-util-visit": "^2.0.3"
|
"unist-util-visit": "^2.0.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@mavrin/remark-typograf/node_modules/unist-util-is": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mavrin/remark-typograf/node_modules/unist-util-visit": {
|
||||||
|
"version": "2.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz",
|
||||||
|
"integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"unist-util-is": "^4.0.0",
|
||||||
|
"unist-util-visit-parents": "^3.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@mavrin/remark-typograf/node_modules/unist-util-visit-parents": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/unist": "^2.0.0",
|
||||||
|
"unist-util-is": "^4.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/unified"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@@ -4773,16 +4812,6 @@
|
|||||||
"integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
|
"integrity": "sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/unist-util-is": {
|
|
||||||
"version": "4.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==",
|
|
||||||
"dev": true,
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/unified"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/unist-util-stringify-position": {
|
"node_modules/unist-util-stringify-position": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.3.tgz",
|
||||||
@@ -4796,35 +4825,6 @@
|
|||||||
"url": "https://opencollective.com/unified"
|
"url": "https://opencollective.com/unified"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/unist-util-visit": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz",
|
|
||||||
"integrity": "sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/unist": "^2.0.0",
|
|
||||||
"unist-util-is": "^4.0.0",
|
|
||||||
"unist-util-visit-parents": "^3.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/unified"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/unist-util-visit-parents": {
|
|
||||||
"version": "3.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz",
|
|
||||||
"integrity": "sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==",
|
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/unist": "^2.0.0",
|
|
||||||
"unist-util-is": "^4.0.0"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/unified"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/unplugin": {
|
"node_modules/unplugin": {
|
||||||
"version": "1.14.1",
|
"version": "1.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.14.1.tgz",
|
||||||
|
|||||||
9
src/lib/components/ArticleTitle.svelte
Normal file
9
src/lib/components/ArticleTitle.svelte
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<script>
|
||||||
|
export let metadata;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="text-xs">
|
||||||
|
{metadata.date}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h1>{metadata.title}</h1>
|
||||||
@@ -5,7 +5,6 @@
|
|||||||
import {page} from '$app/stores';
|
import {page} from '$app/stores';
|
||||||
|
|
||||||
export let homepage: boolean = false
|
export let homepage: boolean = false
|
||||||
export let title: string | null = null
|
|
||||||
|
|
||||||
let nav: HTMLElement;
|
let nav: HTMLElement;
|
||||||
|
|
||||||
@@ -55,9 +54,9 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if title}
|
<h1 class="text-2xl">
|
||||||
<h1 class="text-2xl">{title}</h1>
|
<slot name="title"></slot>
|
||||||
{/if}
|
</h1>
|
||||||
|
|
||||||
{#if !$page.url.pathname.startsWith('/blog')}
|
{#if !$page.url.pathname.startsWith('/blog')}
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import "./prism-one-dark.css";
|
|||||||
import Navbar from "$lib/components/Navbar.svelte";
|
import Navbar from "$lib/components/Navbar.svelte";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Navbar title="Блог"></Navbar>
|
<Navbar>
|
||||||
|
<div slot="title">
|
||||||
|
<a href="/blog">Блог</a>
|
||||||
|
</div>
|
||||||
|
</Navbar>
|
||||||
|
|
||||||
<article class="px-2 py-8 md:px-8 max-w-[980px] mx-auto text-lg">
|
<article class="px-2 py-8 md:px-8 max-w-[980px] mx-auto text-lg">
|
||||||
<slot />
|
<slot />
|
||||||
@@ -11,7 +15,7 @@ import Navbar from "$lib/components/Navbar.svelte";
|
|||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
:global(h1) {
|
:global(h1) {
|
||||||
@apply text-3xl my-6;
|
@apply text-5xl font-semibold my-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(h2) {
|
:global(h2) {
|
||||||
@@ -26,6 +30,10 @@ import Navbar from "$lib/components/Navbar.svelte";
|
|||||||
@apply text-xl my-3;
|
@apply text-xl my-3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(pre) {
|
||||||
|
max-width: calc(100vw - 16px);
|
||||||
|
}
|
||||||
|
|
||||||
:global(pre, code) {
|
:global(pre, code) {
|
||||||
@apply rounded-lg;
|
@apply rounded-lg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import {metadata as htmlInCssMetadata} from "./html-in-css/+page.svx"
|
import {metadata as htmlInCssMetadata} from "./html-in-css/+page.svx"
|
||||||
|
import {metadata as thisBlogMetadata} from "./this-blog/+page.svx"
|
||||||
|
|
||||||
const posts = [
|
const posts = [
|
||||||
|
{
|
||||||
|
href: '/blog/this-blog',
|
||||||
|
...thisBlogMetadata
|
||||||
|
},
|
||||||
{
|
{
|
||||||
href: '/blog/html-in-css',
|
href: '/blog/html-in-css',
|
||||||
...htmlInCssMetadata
|
...htmlInCssMetadata
|
||||||
|
|||||||
@@ -3,9 +3,18 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="px-2 md:px-8 mb-16 max-w-[1280px] mx-auto">
|
<section class="px-2 md:px-8 mb-16 max-w-[1280px] mx-auto">
|
||||||
|
<h2 class="text-4xl mb-10 md:text-5xl">
|
||||||
|
Статьи
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-14">
|
||||||
{#each data.posts as post}
|
{#each data.posts as post}
|
||||||
<a href={post.href}>
|
<a
|
||||||
<h2 class="text-3xl mb-4 md:text-4xl">{post.title}</h2>
|
href={post.href}
|
||||||
|
>
|
||||||
|
<div class="text-sm">{post.date}</div>
|
||||||
|
<h4 class="text-2xl md:text-3xl">{post.title}</h4>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ title: "HTML in CSS: как сократить HTML-код и писать то
|
|||||||
date: "2024-08-01"
|
date: "2024-08-01"
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="text-xs">
|
<script>
|
||||||
{date}
|
import ArticleTitle from "$lib/components/ArticleTitle.svelte"
|
||||||
</div>
|
</script>
|
||||||
|
|
||||||
# {title}
|
<ArticleTitle metadata={metadata}/>
|
||||||
|
|
||||||
## О проекте
|
## О проекте
|
||||||
|
|
||||||
|
|||||||
207
src/routes/blog/this-blog/+page.svx
Normal file
207
src/routes/blog/this-blog/+page.svx
Normal file
@@ -0,0 +1,207 @@
|
|||||||
|
---
|
||||||
|
title: "Как сделан этот блог?"
|
||||||
|
date: "2024-10-04"
|
||||||
|
---
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ArticleTitle from "$lib/components/ArticleTitle.svelte"
|
||||||
|
import CustomTabs from "./CustomTabs.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ArticleTitle metadata={metadata} />
|
||||||
|
|
||||||
|
## Предисловие
|
||||||
|
|
||||||
|
Существует такой удивительный формат под названием MDX. Он представляет из себя смесь JSX и Markdown,
|
||||||
|
отсюда и MDX.
|
||||||
|
|
||||||
|
Он удобен тем, что на нем легко писать форматированный текст, но при этом не надо отказываться от возможности писать сложные
|
||||||
|
интерактивные компоненты, поскольку их можно вставить как обычный React JSX прямо в тело MDX файла.
|
||||||
|
|
||||||
|
<div class='grid md:grid-cols-2 gap-4'>
|
||||||
|
<div>
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### Пример статьи
|
||||||
|
|
||||||
|
<CustomTabs>
|
||||||
|
<CustomTab
|
||||||
|
value={1}
|
||||||
|
>
|
||||||
|
Да, можно **прямо тут** форматировать!
|
||||||
|
</CustomTab>
|
||||||
|
<CustomTab
|
||||||
|
value={2}
|
||||||
|
>
|
||||||
|
Или оставлять [ссылки](https://kopyl.dev)
|
||||||
|
привычным способом
|
||||||
|
</CustomTab>
|
||||||
|
</CustomTabs>
|
||||||
|
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class='my-2 p-4'>
|
||||||
|
|
||||||
|
<h3 class='text-2xl'>Пример статьи</h3>
|
||||||
|
|
||||||
|
<CustomTabs>
|
||||||
|
<div slot="1">
|
||||||
|
|
||||||
|
Да, можно **прямо тут** форматировать!
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div slot="2">
|
||||||
|
|
||||||
|
Или оставлять [ссылки](https://kopyl.dev) привычным способом
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</CustomTabs>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
Есть целый ряд фреймворков, которые позволяют писать на нем, а для особо ушлых, конечно, есть и варианты без фреймворка.
|
||||||
|
MDX, в конце концов, просто компилируется в обычный React JSX, так что прикрутив этот компилятор MDX к обычному React проекту,
|
||||||
|
можно начать писать в нем MDX.
|
||||||
|
|
||||||
|
## А как все таки сделан этот блог?
|
||||||
|
|
||||||
|
Ведь эта страница, как и остальные на этом сайте, написана на Svelte!
|
||||||
|
|
||||||
|
К счастью для меня и остальных фанатов MDX, есть подобная альтернатива, интегрирующая Markdown и Svelte: <a href="https://github.com/pngwn/MDsveX" target="_blank">MDsveX</a>.
|
||||||
|
|
||||||
|
Хоть название и не лучшее, возможности не уступают оригиналу.
|
||||||
|
Самое главное, что у SVX (я буду так его называть), есть возможность передавать в компилятор <a href="https://github.com/remarkjs/remark" target="_blank">remark</a> и <a href="https://github.com/rehypejs/rehype" target="_blank">rehype</a> плагины.
|
||||||
|
|
||||||
|
## Создание проекта
|
||||||
|
|
||||||
|
### 1. Инициализация проекта
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm create svelte@latest my-blog
|
||||||
|
cd my-blog
|
||||||
|
```
|
||||||
|
|
||||||
|
Инициализируем проект на SvelteKit и переходим в каталог с ним.
|
||||||
|
|
||||||
|
### 2. Установка зависимостей
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i -D mdsvex @mavrin/remark-typograf
|
||||||
|
```
|
||||||
|
|
||||||
|
Ставим наши зависимости, про вторую чуть позже.
|
||||||
|
|
||||||
|
### 3. Настройка конфигов
|
||||||
|
|
||||||
|
```js
|
||||||
|
// svelte.config.js
|
||||||
|
import adapter from "@sveltejs/adapter-auto";
|
||||||
|
import {vitePreprocess} from '@sveltejs/vite-plugin-svelte';
|
||||||
|
import {mdsvex} from "mdsvex";
|
||||||
|
import remarkTypograf from "@mavrin/remark-typograf";
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
extensions: [
|
||||||
|
".svelte",
|
||||||
|
".svx"
|
||||||
|
],
|
||||||
|
preprocess: [
|
||||||
|
vitePreprocess(),
|
||||||
|
mdsvex({
|
||||||
|
remarkPlugins: [remarkTypograf]
|
||||||
|
})
|
||||||
|
],
|
||||||
|
kit: {
|
||||||
|
adapter: adapter()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
|
```
|
||||||
|
|
||||||
|
Отличается от стандартного конфига лишь тем, что мы добавили `mdsvex` в `preprocess` и закинули `.svx` в `extensions`.
|
||||||
|
|
||||||
|
### 4. Создание страницы
|
||||||
|
|
||||||
|
Создадим каталог `src/routes/blog`, в нем будут все наши статьи.
|
||||||
|
Чтобы создать страницу, создадим в этом каталоге каталог с произвольным именем `this-blog` и в нем файл `+page.svx`.
|
||||||
|
В нем можно писать как будто это обычный `.svelte` файл, но весь Markdown будет на выходе форматирован.
|
||||||
|
|
||||||
|
Помимо этого SVX поддерживает Frontmatter -- в начале файла напишем блок выделенный минусами.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: "Как сделан этот блог?"
|
||||||
|
date: "2024-10-02"
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
Теперь эти поля доступны как переменные как в документе, так и для экспорта внутри объекта `metadata`.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
title: "Как сделан этот блог?"
|
||||||
|
date: "2024-10-02"
|
||||||
|
---
|
||||||
|
|
||||||
|
<h1>{title}</h1>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Создание списка статей
|
||||||
|
|
||||||
|
Теперь создадим файл `+page.server.ts` в каталоге `src/routes/blog`. Он будет предоставлять странице список всех статей.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/routes/blog/+page.server.ts
|
||||||
|
import {metadata as thisBlogMetadata} from "./this-blog/+page.svx"
|
||||||
|
|
||||||
|
const posts = [
|
||||||
|
{
|
||||||
|
href: "/blog/this-blog",
|
||||||
|
...thisBlogMetadata
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const load = async () => {
|
||||||
|
return {
|
||||||
|
posts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
А в файл `+page.svelte` используем переданный список.
|
||||||
|
|
||||||
|
```html
|
||||||
|
// src/routes/blog/+page.svelte
|
||||||
|
<script>
|
||||||
|
export let data;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<h2>
|
||||||
|
Статьи
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-14">
|
||||||
|
{#each data.posts as post}
|
||||||
|
<a
|
||||||
|
href={post.href}
|
||||||
|
>
|
||||||
|
<div class="text-sm">{post.date}</div>
|
||||||
|
<h4 class="text-2xl md:text-3xl">{post.title}</h4>
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
Готово!
|
||||||
|
|
||||||
|
## Наслаждаемся результатами
|
||||||
|
|
||||||
|
В нашем блоге настроен <a href="https://github.com/typograf/typograf" target="_blank">типограф</a>, что позволяет нам не заботиться о переносах строк в неположенных местах,
|
||||||
|
он заполняет текст неразрывными пробелами где надо.
|
||||||
|
Помимо этого, он заменяет `"` на `»`, выносит кавычки за пределы ссылки, заменяет `...` на `…`, превращает `--` в `—`.
|
||||||
|
|
||||||
|
Это только наиболее часто встречающиеся исправления, помимо них еще огромное количество исправлений и замен.
|
||||||
18
src/routes/blog/this-blog/CustomTabs.svelte
Normal file
18
src/routes/blog/this-blog/CustomTabs.svelte
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let firstTabActive = true;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="p-8 border rounded-t-lg rounded-br-lg mt-8">
|
||||||
|
{#if firstTabActive}
|
||||||
|
<slot name="1"></slot>
|
||||||
|
{:else}
|
||||||
|
<slot name="2"></slot>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
on:click={() => firstTabActive = !firstTabActive}
|
||||||
|
class="p-4 border rounded-b-lg bg-slate-800 hover:bg-slate-700"
|
||||||
|
>
|
||||||
|
Сменить таб
|
||||||
|
</button>
|
||||||
@@ -9,7 +9,9 @@ const config = {
|
|||||||
preprocess: [
|
preprocess: [
|
||||||
vitePreprocess(),
|
vitePreprocess(),
|
||||||
mdsvex({
|
mdsvex({
|
||||||
remarkPlugins: [remarkTypograf]
|
remarkPlugins: [
|
||||||
|
remarkTypograf,
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
kit: {
|
kit: {
|
||||||
|
|||||||
Reference in New Issue
Block a user