mirror of
https://github.com/DefectingCat/DefectingCat.github.io
synced 2025-07-15 16:51:37 +00:00
Add support image lazyloading
This commit is contained in:
@ -3,10 +3,15 @@ import NextImage, { ImageProps } from 'next/image';
|
|||||||
interface Props extends ImageProps {}
|
interface Props extends ImageProps {}
|
||||||
|
|
||||||
const Image = ({ alt, ...rest }: Props) => {
|
const Image = ({ alt, ...rest }: Props) => {
|
||||||
|
const supportImg = ['jpeg', 'png', 'webp', 'avif'];
|
||||||
|
const placeholder = supportImg.includes((rest.src as { src: string }).src)
|
||||||
|
? 'blur'
|
||||||
|
: 'empty';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className="block text-center">
|
<span className="block text-center">
|
||||||
<NextImage alt={alt} placeholder="blur" {...rest} />
|
<NextImage alt={alt} placeholder={placeholder} {...rest} />
|
||||||
{alt && <span className="block text-center text-gray-400">{alt}</span>}
|
{alt && <span className="block text-center text-gray-400">{alt}</span>}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
|
@ -14,7 +14,10 @@ const composedConfig = composePlugins([
|
|||||||
extension: /\.mdx?$/,
|
extension: /\.mdx?$/,
|
||||||
options: {
|
options: {
|
||||||
remarkPlugins: [remarkFrontmatter, remarkGfm],
|
remarkPlugins: [remarkFrontmatter, remarkGfm],
|
||||||
rehypePlugins: [rehypePrism, rehypeSlug],
|
rehypePlugins: [
|
||||||
|
[rehypePrism, { alias: { vue: 'xml' }, ignoreMissing: true }],
|
||||||
|
rehypeSlug,
|
||||||
|
],
|
||||||
providerImportSource: '@mdx-js/react',
|
providerImportSource: '@mdx-js/react',
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
328
pages/p/create-a-mini-router-for-react.mdx
Normal file
328
pages/p/create-a-mini-router-for-react.mdx
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
---
|
||||||
|
title: 现代前端的Web应用路由-为React打造一个迷你路由器
|
||||||
|
date: '2022-08-03'
|
||||||
|
tags: [JavaScript, React]
|
||||||
|
---
|
||||||
|
|
||||||
|
import Layout from 'layouts/MDXLayout';
|
||||||
|
import Image from 'components/mdx/Image';
|
||||||
|
import image1 from 'public/images/p/create-a-mini-router-for-react/router.webp';
|
||||||
|
import image2 from 'public/images/p/create-a-mini-router-for-react/Web架构.svg';
|
||||||
|
import image3 from 'public/images/p/create-a-mini-router-for-react/迷你路由器.svg';
|
||||||
|
import image4 from 'public/images/p/create-a-mini-router-for-react/image-20210823154009498.webp';
|
||||||
|
|
||||||
|
export const meta = {
|
||||||
|
title: '现代前端的Web应用路由-为React打造一个迷你路由器',
|
||||||
|
date: '2022-08-03',
|
||||||
|
tags: ['JavaScript', 'React'],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ({ children }) => <Layout {...meta}>{children}</Layout>;
|
||||||
|
|
||||||
|
路由不仅仅只是网络的代名词,它更像是一种表达路径的概念。与网络中的路由相似,前端中的页面路由也是带领我们前往指定的地方。
|
||||||
|
|
||||||
|
<Image src={image1} placeholder="" priority />
|
||||||
|
|
||||||
|
## 现代前端的 Web 应用路由
|
||||||
|
|
||||||
|
时代在变迁,过去,Web 应用的基本架构使用了一种不同于现代路由的方法。曾经的架构通常都是由后端生成的 HTML 模板来发送给浏览器。当我们单击一个标签导航到另一个页面时,浏览器会发送一个新的请求给服务器,然后服务器再将对应的页面渲染好发过来。也就是说,每个请求都会刷新页面。
|
||||||
|
|
||||||
|
自那时起,Web 服务器在设计和构造方面经历了很多发展(前端也是)。如今,JavaScript 框架和浏览器技术已经足够先进,允许我们利用 JavaScript 在客户端渲染 HTML 页面,以至于 Web 应用可以采用更独特的前后的分离机制。在第一次由服务端下发了对应的 JavaScript 代码后,后续的工作就全部交给客户端 JavaScript。而后端服务器负责发送原始数据,通常是 JSON 等。
|
||||||
|
|
||||||
|
<Image src={image2} alt="Web架构" />
|
||||||
|
|
||||||
|
在旧架构中,动态内容由 Web 服务器生成,服务器会在数据库中获取数据,并利用数据渲染 HTML 模板发送给浏览器。每次切换页面都会获取新的由服务端渲染的页面发送给浏览器。
|
||||||
|
|
||||||
|
在新架构中,服务端通常只下发主要的 JavaScript 和基本的 HTML 框架。之后页面的渲染就会由我们的 JavaScript 接管,后续的动态内容也还是由服务器在数据库中获取,但不同的是,后续数据由服务器发送原始格式(JSON 等)。前端 JavaScript 由 AJAX 等技术获取到了新数据,再在客户端完成新的 HTML 渲染。
|
||||||
|
|
||||||
|
好像 SPA 的大概就是将原先服务端干的活交给了前端的 JavaScript 来做。事实上,确实如此。AJAX 技术的发展,带动了客户端 JavaScript 崛起,使得原先需要在服务端才能完成渲染的动态内容,现在交给 JavaScript 就可以了。
|
||||||
|
|
||||||
|
这么做的好处有很多:
|
||||||
|
|
||||||
|
- 主要渲染工作在客户端,减少服务器的压力。简单的场景甚至只需要静态服务器。
|
||||||
|
- 新内容获取只需要少量交互,而不是服务端发送渲染好的 HTML 页面。
|
||||||
|
- 可以利用 JavaScript 在客户端修改和渲染任意内容,同时无需刷新整个页面。
|
||||||
|
- ……
|
||||||
|
|
||||||
|
当然同时也有一些缺点,目前最主要的痛点:
|
||||||
|
|
||||||
|
- 首屏/白屏时间:由于 HTML 内容需要客户端 JavaScript 完成渲染,前端架构以及多种因素会影响首次内容的出现时间。
|
||||||
|
- 爬虫/SEO:由于 HTML 内容需要客户端 JavaScript 完成渲染,早期的爬虫可能不会在浏览器环境下执行 JavaScript,这就导致了根本爬取不到 HTML 内容。
|
||||||
|
|
||||||
|
> Google 的爬虫貌似已经可以爬取 SPA。
|
||||||
|
|
||||||
|
## React 的路由
|
||||||
|
|
||||||
|
React 是现代众多成熟的 SPA 应用框架之一,它自然也需要使用路由来切换对应的组件。React Router 是一个成熟的 React 路由组件库,它实现了许多功能,同时也非常还用。
|
||||||
|
|
||||||
|
首先来看下本次路由的基本工作原理,本质上很简单,我们需要一个可以渲染任意组件的父组件 `<Router />` 或者叫 `<router-view>` 之类的。然后再根据浏览器地址的变化,渲染注册路由时对应的组件即可。
|
||||||
|
|
||||||
|
<Image src={image3} alt="迷你路由器" />
|
||||||
|
|
||||||
|
### 配置文件
|
||||||
|
|
||||||
|
这里选择类似 Vue Router 的配置文件风格,而不是使用类似 `<Route />` 这样的 DOM 结构来注册路由。我们的目的是为了实现一个非常简单的迷你路由器,所以配置文件自然也就很简单:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { lazy } from 'react';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
component: lazy(() => import('../pages/Home')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
component: lazy(() => import('../pages/About')),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
一个 `path` 属性,对应了浏览器的地址,或者说 `location.pathname`;一个 `component` 属性,对应了到该地址时所需要渲染的组件。
|
||||||
|
|
||||||
|
甚至还可以使用 `lazy()` 配合 `<Suspense>` 来实现代码分割。
|
||||||
|
|
||||||
|
### 展示路由
|
||||||
|
|
||||||
|
一个非常简单的路由就这样注册好了,接下来就是将对应的组件展示出来。我们都知道,JSX 最终会被 babel 转义为渲染函数,而一个组件的 `<Home />` 写法,基本等同于 `React.createElement(Home)`。[元素渲染 – React (reactjs.org)](https://zh-hans.reactjs.org/docs/rendering-elements.html#updating-the-rendered-element)
|
||||||
|
|
||||||
|
所以动态的渲染指定的组件基本上也就很容易解决,接下来的思路也就很简单了,我们需要:
|
||||||
|
|
||||||
|
- 一个状态:记录当前地址 `location.pathname`;
|
||||||
|
- 根据当前地址在配置文件中寻找对应注册的组件,并将它渲染出来;
|
||||||
|
- 一个副作用:当用户手动切换路由时,该组件需要重新渲染为对应注册的路由;
|
||||||
|
|
||||||
|
先不考虑切换路由的问题,前两个基本上已经就实现了:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// Router.tsx
|
||||||
|
import React, { useState, Suspense } from 'react';
|
||||||
|
import routes from './routes';
|
||||||
|
|
||||||
|
const Router: React.FC = () => {
|
||||||
|
// 获取地址,并保存到状态中
|
||||||
|
const [path, setPath] = useState(location.pathname);
|
||||||
|
// 根据地址,寻找对应的组件
|
||||||
|
const element = routes.find((route) => route.path === path)?.component;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Suspense fallback={<p>loading...</p>}>
|
||||||
|
{/* 使用 React.createElement() 渲染组件 */}
|
||||||
|
{element ? React.createElement(element) : void 0}
|
||||||
|
</Suspense>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Router;
|
||||||
|
```
|
||||||
|
|
||||||
|
看上去很简单,事实上,确实很简单。
|
||||||
|
|
||||||
|
现在我们直接从地址栏访问对应的路由,我们的 Router 组件应该就可以根据已经注册好的路由配置文件来找到正确的组件,并将其渲染出来了。
|
||||||
|
|
||||||
|
<Image src={image4} alt="image-20210823154009498" />
|
||||||
|
|
||||||
|
### 切换路由
|
||||||
|
|
||||||
|
到目前为止,我们实现了根据对应地址访问到对应组件的功能。这是一个路由必不可少的功能,但它还不能称得上是一个简单的路由器,因为它还无法处理用户手动切换的路由,也就是点击标签前往对应的页面。
|
||||||
|
|
||||||
|
简单梳理一下我们需要实现的功能:
|
||||||
|
|
||||||
|
- 一个 Link 组件,用于点击后导航到指定的地址;
|
||||||
|
- 导航到地址后,还要修改浏览器的地址栏,并不真正的发送请求;
|
||||||
|
- 通知 Router 组件,地址已经改变,重新渲染对应路由的组件;
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
to: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RouterLink: React.FC<Props> = ({ to, children }: Props) => {
|
||||||
|
/**
|
||||||
|
* 处理点击事件
|
||||||
|
* 创建自定义事件监听器
|
||||||
|
* 并将 path 发送给 router
|
||||||
|
* @param e
|
||||||
|
*/
|
||||||
|
const handleClick = (e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const nowPath = location.pathname;
|
||||||
|
if (nowPath === to) return; // 原地跳转
|
||||||
|
|
||||||
|
history.pushState(null, '', to);
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent<string>('route', {
|
||||||
|
detail: to,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<a href="" onClick={handleClick}>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RouterLink;
|
||||||
|
```
|
||||||
|
|
||||||
|
我们将 Link 组件实际的渲染为一个 `<a></a>` 标签,这样就能模拟跳转到指定的导航了。这个组件它接收两个参数:`{ to, children }`,分别是前往的路由地址和标签的内容。标签的内容 children 就是展示在 a 标签内的文本。
|
||||||
|
|
||||||
|
我们需要解决的第一个问题就是点击标签后跳转到指定的导航,这里其实需要分成两个部分,第一个部分是悄悄的修改浏览器地址栏,第二个部分则是通知 Router 组件去渲染对应的组件。
|
||||||
|
|
||||||
|
修改地址栏很简单,利用到浏览器的 history API,可以很方便的修改 `pathName` 而不发送实际请求,这里只需要修改到第一个参数 to 即可:`history.pushState(null, '', to);`。
|
||||||
|
|
||||||
|
但使用 `pushState()` 并不会发出任何通知,我们需要自己实现去通知 Router 组件地址已经变化。本来像利用第三方库来实现一个 发布/订阅 的模型的,但这样这个路由器可能就没有那么迷你了。最后发现利用 HTML 的 CustomEvent 可以实现一个简单的消息订阅与发布模型。
|
||||||
|
|
||||||
|
HTML 自定义事件也很简单,我们在对应的 DOM 上 `dispatchEvent` 即可触发一个事件,而触发的这个事件,就是 `CustomEvent` 的实例,甚至还能传递一些信息。在这里,我们将路由地址传递过去。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
document.dispatchEvent(
|
||||||
|
new CustomEvent<string>('route', {
|
||||||
|
detail: to,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
而在 Router 组件中,只需要和以前一样在对应的 DOM 上去监听一个事件,这个事件就是刚刚发布的 `CustomEvent` 的实例。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
// Router.tsx
|
||||||
|
const handleRoute = (e: CustomEvent<string>) => {
|
||||||
|
console.log(e.detail);
|
||||||
|
setPath(e.detail);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
/**
|
||||||
|
* 监听自定义 route 事件
|
||||||
|
* 并根据 path 修改路由
|
||||||
|
*/
|
||||||
|
document.addEventListener('route', handleRoute as EventListener);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// 清除副作用
|
||||||
|
document.removeEventListener('route', handleRoute as EventListener);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
```
|
||||||
|
|
||||||
|
在 Router 组件中,根据接收到的变化,将新的地址保存到状态中,并触发组件重新渲染。
|
||||||
|
|
||||||
|
这样一个最简单的 React 路由就做好了。
|
||||||
|
|
||||||
|
## Vue 的路由
|
||||||
|
|
||||||
|
与 React 同理,二者的路由切换都差不多,其主要思路还是使用自定义事件来订阅路由切换的请求。但 Vue 的具体实现与 React 还是有点不同的。
|
||||||
|
|
||||||
|
### 配置文件
|
||||||
|
|
||||||
|
路由的配置文件还是同理,不同的是,Vue 的异步组件需要在引入时同时引入一个 Loading 组件来实现 Loading 的效果:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineAsyncComponent } from 'vue';
|
||||||
|
import Loading from '../components/common/Loading.vue';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
name: 'Home',
|
||||||
|
component: defineAsyncComponent({
|
||||||
|
loader: () => import('../views/Home.vue'),
|
||||||
|
loadingComponent: Loading,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
name: 'About',
|
||||||
|
component: defineAsyncComponent({
|
||||||
|
loader: () => import('../views/About.vue'),
|
||||||
|
loadingComponent: Loading,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
### 展示路由
|
||||||
|
|
||||||
|
同理,Vue 也是利用根据条件来渲染对应路由的组件。不同的是,我们可以使用模板语法来实现,也可以利用 `render()` 方法来直接渲染组件。
|
||||||
|
|
||||||
|
首先来看看和 React 类似的 `render()` 方法。在 Vue3 中,使用 `setup()` 方法后,可以直接返回一个 `createVNode()` 的函数,这就是 `render()` 方法。所以可以直接写 TypeScript 文件。
|
||||||
|
|
||||||
|
与 React 不同的地方在于,React 每次调用 `setPath(e.detail)` 存储状态时都会重新渲染组件,从而重新执行组件的函数,获取到对应的路由组件。
|
||||||
|
|
||||||
|
但 Vue 不同,如果我们仅仅将路由名称 `e.detail` 保存到状态,但没有实际在 VNode 中使用的话,更新状态时不会重新渲染组件的,也就是说,不会获取到对应的路由组件。所以最佳的办法就是将整个路由组件保存到状态,可保存整个组件无疑太过庞大。好在 Vue3 给了我们另一种解决方法:`shallowRef()`。它会创建一个跟踪自身 `.value` 变化的 ref,但不会使其值也变成响应式的。
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createVNode, defineComponent, shallowRef } from 'vue';
|
||||||
|
import routes from './routes';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'RouterView',
|
||||||
|
setup() {
|
||||||
|
let currentPath = window.location.pathname;
|
||||||
|
const component = shallowRef(
|
||||||
|
routes.find((item) => item.path === currentPath)?.component ??
|
||||||
|
'Note found'
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEvent = (e: CustomEvent<string>) => {
|
||||||
|
console.log(e.detail);
|
||||||
|
currentPath = e.detail;
|
||||||
|
component.value =
|
||||||
|
routes.find((item) => item.path === currentPath)?.component ??
|
||||||
|
'Note found';
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('route', handleEvent as EventListener);
|
||||||
|
|
||||||
|
return () => createVNode(component.value);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
而使用模板语法主要是利用到了全局的 `component` 组件,其他部分与 `render()` 方法相同:
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<component :is="component"></component>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { onUpdated, shallowRef } from 'vue';
|
||||||
|
import routes from './routes';
|
||||||
|
|
||||||
|
let currentPath = window.location.pathname;
|
||||||
|
const component = shallowRef(
|
||||||
|
routes.find((item) => item.path === currentPath)?.component
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleEvent = (e: CustomEvent<string>) => {
|
||||||
|
console.log(e.detail);
|
||||||
|
currentPath = e.detail;
|
||||||
|
component.value = routes.find((item) => item.path === currentPath)?.component;
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('route', handleEvent as EventListener);
|
||||||
|
|
||||||
|
onUpdated(() => {
|
||||||
|
console.log(component);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 总结
|
||||||
|
|
||||||
|
如今的 JavaScript 做能做到的比以前更加强大,配合多种 HTML API,可以将曾经不可能实现的事变为现实。这个简单的迷你路由,主要的思路就是利用 HTML API 来通知 Router 组件该渲染哪个组件了。配合上 `lazy()` 方法,甚至还能实现代码分割。
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
[DefectingCat/react-tiny-router: A tiny react router. (github.com)](https://github.com/DefectingCat/react-tiny-router)
|
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="Electron" modified="2021-08-23T03:10:20.595Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.6.13 Chrome/89.0.4389.128 Electron/12.0.7 Safari/537.36" etag="3TGQTE9oRjaT-yreGNVk" version="14.6.13" type="device"><diagram id="dEYAjTxxdtpzwiRtR6KX" name="第 1 页">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</diagram></mxfile>
|
3
public/images/p/create-a-mini-router-for-react/Web架构.svg
Normal file
3
public/images/p/create-a-mini-router-for-react/Web架构.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 57 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.9 KiB |
BIN
public/images/p/create-a-mini-router-for-react/router.png
Normal file
BIN
public/images/p/create-a-mini-router-for-react/router.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 504 KiB |
BIN
public/images/p/create-a-mini-router-for-react/router.psd
Normal file
BIN
public/images/p/create-a-mini-router-for-react/router.psd
Normal file
Binary file not shown.
BIN
public/images/p/create-a-mini-router-for-react/router.webp
Normal file
BIN
public/images/p/create-a-mini-router-for-react/router.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1 @@
|
|||||||
|
<mxfile host="Electron" modified="2021-08-23T08:52:55.443Z" agent="5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.9.6 Chrome/89.0.4389.128 Electron/12.0.16 Safari/537.36" etag="msaHTrqSDVVvf8JIMCGx" version="14.9.6" type="device"><diagram id="XTkkDmfpvXEnlI4-h4sE" name="第 1 页">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</diagram></mxfile>
|
3
public/images/p/create-a-mini-router-for-react/迷你路由器.svg
Normal file
3
public/images/p/create-a-mini-router-for-react/迷你路由器.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 45 KiB |
398
styles/rua.css
398
styles/rua.css
@ -1,118 +1,118 @@
|
|||||||
#article {
|
#article {
|
||||||
--color-prettylights-syntax-comment: #6e7781;
|
--color-prettylights-syntax-comment: #6e7781;
|
||||||
--color-prettylights-syntax-constant: #0550ae;
|
--color-prettylights-syntax-constant: #0550ae;
|
||||||
--color-prettylights-syntax-entity: #8250df;
|
--color-prettylights-syntax-entity: #8250df;
|
||||||
--color-prettylights-syntax-storage-modifier-import: #24292f;
|
--color-prettylights-syntax-storage-modifier-import: #24292f;
|
||||||
--color-prettylights-syntax-entity-tag: #116329;
|
--color-prettylights-syntax-entity-tag: #116329;
|
||||||
--color-prettylights-syntax-keyword: #cf222e;
|
--color-prettylights-syntax-keyword: #cf222e;
|
||||||
--color-prettylights-syntax-string: #0a3069;
|
--color-prettylights-syntax-string: #0a3069;
|
||||||
--color-prettylights-syntax-variable: #953800;
|
--color-prettylights-syntax-variable: #953800;
|
||||||
--color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
|
--color-prettylights-syntax-brackethighlighter-unmatched: #82071e;
|
||||||
--color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
|
--color-prettylights-syntax-invalid-illegal-text: #f6f8fa;
|
||||||
--color-prettylights-syntax-invalid-illegal-bg: #82071e;
|
--color-prettylights-syntax-invalid-illegal-bg: #82071e;
|
||||||
--color-prettylights-syntax-carriage-return-text: #f6f8fa;
|
--color-prettylights-syntax-carriage-return-text: #f6f8fa;
|
||||||
--color-prettylights-syntax-carriage-return-bg: #cf222e;
|
--color-prettylights-syntax-carriage-return-bg: #cf222e;
|
||||||
--color-prettylights-syntax-string-regexp: #116329;
|
--color-prettylights-syntax-string-regexp: #116329;
|
||||||
--color-prettylights-syntax-markup-list: #3b2300;
|
--color-prettylights-syntax-markup-list: #3b2300;
|
||||||
--color-prettylights-syntax-markup-heading: #0550ae;
|
--color-prettylights-syntax-markup-heading: #0550ae;
|
||||||
--color-prettylights-syntax-markup-italic: #24292f;
|
--color-prettylights-syntax-markup-italic: #24292f;
|
||||||
--color-prettylights-syntax-markup-bold: #24292f;
|
--color-prettylights-syntax-markup-bold: #24292f;
|
||||||
--color-prettylights-syntax-markup-deleted-text: #82071e;
|
--color-prettylights-syntax-markup-deleted-text: #82071e;
|
||||||
--color-prettylights-syntax-markup-deleted-bg: #ffebe9;
|
--color-prettylights-syntax-markup-deleted-bg: #ffebe9;
|
||||||
--color-prettylights-syntax-markup-inserted-text: #116329;
|
--color-prettylights-syntax-markup-inserted-text: #116329;
|
||||||
--color-prettylights-syntax-markup-inserted-bg: #dafbe1;
|
--color-prettylights-syntax-markup-inserted-bg: #dafbe1;
|
||||||
--color-prettylights-syntax-markup-changed-text: #953800;
|
--color-prettylights-syntax-markup-changed-text: #953800;
|
||||||
--color-prettylights-syntax-markup-changed-bg: #ffd8b5;
|
--color-prettylights-syntax-markup-changed-bg: #ffd8b5;
|
||||||
--color-prettylights-syntax-markup-ignored-text: #eaeef2;
|
--color-prettylights-syntax-markup-ignored-text: #eaeef2;
|
||||||
--color-prettylights-syntax-markup-ignored-bg: #0550ae;
|
--color-prettylights-syntax-markup-ignored-bg: #0550ae;
|
||||||
--color-prettylights-syntax-meta-diff-range: #8250df;
|
--color-prettylights-syntax-meta-diff-range: #8250df;
|
||||||
--color-prettylights-syntax-brackethighlighter-angle: #57606a;
|
--color-prettylights-syntax-brackethighlighter-angle: #57606a;
|
||||||
--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
|
--color-prettylights-syntax-sublimelinter-gutter-mark: #8c959f;
|
||||||
--color-prettylights-syntax-constant-other-reference-link: #0a3069;
|
--color-prettylights-syntax-constant-other-reference-link: #0a3069;
|
||||||
--color-fg-default: #24292f;
|
--color-fg-default: #24292f;
|
||||||
--color-fg-muted: #57606a;
|
--color-fg-muted: #57606a;
|
||||||
--color-fg-subtle: #6e7781;
|
--color-fg-subtle: #6e7781;
|
||||||
--color-canvas-default: #ffffff;
|
--color-canvas-default: #ffffff;
|
||||||
--color-canvas-subtle: #f6f8fa;
|
--color-canvas-subtle: #f6f8fa;
|
||||||
--color-border-default: #d0d7de;
|
--color-border-default: #d0d7de;
|
||||||
--color-border-muted: hsla(210, 18%, 87%, 1);
|
--color-border-muted: hsla(210, 18%, 87%, 1);
|
||||||
--color-neutral-muted: rgba(175, 184, 193, 0.2);
|
--color-neutral-muted: rgba(175, 184, 193, 0.2);
|
||||||
--color-accent-fg: #0969da;
|
--color-accent-fg: #0969da;
|
||||||
--color-accent-emphasis: #0969da;
|
--color-accent-emphasis: #0969da;
|
||||||
--color-attention-subtle: #fff8c5;
|
--color-attention-subtle: #fff8c5;
|
||||||
--color-danger-fg: #cf222e;
|
--color-danger-fg: #cf222e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark #article {
|
.dark #article {
|
||||||
--color-prettylights-syntax-comment: #8b949e;
|
--color-prettylights-syntax-comment: #8b949e;
|
||||||
--color-prettylights-syntax-constant: #79c0ff;
|
--color-prettylights-syntax-constant: #79c0ff;
|
||||||
--color-prettylights-syntax-entity: #d2a8ff;
|
--color-prettylights-syntax-entity: #d2a8ff;
|
||||||
--color-prettylights-syntax-storage-modifier-import: #c9d1d9;
|
--color-prettylights-syntax-storage-modifier-import: #c9d1d9;
|
||||||
--color-prettylights-syntax-entity-tag: #7ee787;
|
--color-prettylights-syntax-entity-tag: #7ee787;
|
||||||
--color-prettylights-syntax-keyword: #ff7b72;
|
--color-prettylights-syntax-keyword: #ff7b72;
|
||||||
--color-prettylights-syntax-string: #a5d6ff;
|
--color-prettylights-syntax-string: #a5d6ff;
|
||||||
--color-prettylights-syntax-variable: #ffa657;
|
--color-prettylights-syntax-variable: #ffa657;
|
||||||
--color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
|
--color-prettylights-syntax-brackethighlighter-unmatched: #f85149;
|
||||||
--color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
|
--color-prettylights-syntax-invalid-illegal-text: #f0f6fc;
|
||||||
--color-prettylights-syntax-invalid-illegal-bg: #8e1519;
|
--color-prettylights-syntax-invalid-illegal-bg: #8e1519;
|
||||||
--color-prettylights-syntax-carriage-return-text: #f0f6fc;
|
--color-prettylights-syntax-carriage-return-text: #f0f6fc;
|
||||||
--color-prettylights-syntax-carriage-return-bg: #b62324;
|
--color-prettylights-syntax-carriage-return-bg: #b62324;
|
||||||
--color-prettylights-syntax-string-regexp: #7ee787;
|
--color-prettylights-syntax-string-regexp: #7ee787;
|
||||||
--color-prettylights-syntax-markup-list: #f2cc60;
|
--color-prettylights-syntax-markup-list: #f2cc60;
|
||||||
--color-prettylights-syntax-markup-heading: #1f6feb;
|
--color-prettylights-syntax-markup-heading: #1f6feb;
|
||||||
--color-prettylights-syntax-markup-italic: #c9d1d9;
|
--color-prettylights-syntax-markup-italic: #c9d1d9;
|
||||||
--color-prettylights-syntax-markup-bold: #c9d1d9;
|
--color-prettylights-syntax-markup-bold: #c9d1d9;
|
||||||
--color-prettylights-syntax-markup-deleted-text: #ffdcd7;
|
--color-prettylights-syntax-markup-deleted-text: #ffdcd7;
|
||||||
--color-prettylights-syntax-markup-deleted-bg: #67060c;
|
--color-prettylights-syntax-markup-deleted-bg: #67060c;
|
||||||
--color-prettylights-syntax-markup-inserted-text: #aff5b4;
|
--color-prettylights-syntax-markup-inserted-text: #aff5b4;
|
||||||
--color-prettylights-syntax-markup-inserted-bg: #033a16;
|
--color-prettylights-syntax-markup-inserted-bg: #033a16;
|
||||||
--color-prettylights-syntax-markup-changed-text: #ffdfb6;
|
--color-prettylights-syntax-markup-changed-text: #ffdfb6;
|
||||||
--color-prettylights-syntax-markup-changed-bg: #5a1e02;
|
--color-prettylights-syntax-markup-changed-bg: #5a1e02;
|
||||||
--color-prettylights-syntax-markup-ignored-text: #c9d1d9;
|
--color-prettylights-syntax-markup-ignored-text: #c9d1d9;
|
||||||
--color-prettylights-syntax-markup-ignored-bg: #1158c7;
|
--color-prettylights-syntax-markup-ignored-bg: #1158c7;
|
||||||
--color-prettylights-syntax-meta-diff-range: #d2a8ff;
|
--color-prettylights-syntax-meta-diff-range: #d2a8ff;
|
||||||
--color-prettylights-syntax-brackethighlighter-angle: #8b949e;
|
--color-prettylights-syntax-brackethighlighter-angle: #8b949e;
|
||||||
--color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
|
--color-prettylights-syntax-sublimelinter-gutter-mark: #484f58;
|
||||||
--color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
|
--color-prettylights-syntax-constant-other-reference-link: #a5d6ff;
|
||||||
--color-fg-default: #c9d1d9;
|
--color-fg-default: #c9d1d9;
|
||||||
--color-fg-muted: #8b949e;
|
--color-fg-muted: #8b949e;
|
||||||
--color-fg-subtle: #484f58;
|
--color-fg-subtle: #484f58;
|
||||||
--color-canvas-default: #0d1117;
|
--color-canvas-default: #0d1117;
|
||||||
--color-canvas-subtle: #161b22;
|
--color-canvas-subtle: #161b22;
|
||||||
--color-border-default: #30363d;
|
--color-border-default: #30363d;
|
||||||
--color-border-muted: #21262d;
|
--color-border-muted: #21262d;
|
||||||
--color-neutral-muted: rgba(110, 118, 129, 0.4);
|
--color-neutral-muted: rgba(110, 118, 129, 0.4);
|
||||||
--color-accent-fg: #58a6ff;
|
--color-accent-fg: #58a6ff;
|
||||||
--color-accent-emphasis: #1f6feb;
|
--color-accent-emphasis: #1f6feb;
|
||||||
--color-attention-subtle: rgba(187, 128, 9, 0.15);
|
--color-attention-subtle: rgba(187, 128, 9, 0.15);
|
||||||
--color-danger-fg: #f85149;
|
--color-danger-fg: #f85149;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article {
|
#article {
|
||||||
@apply text-lg leading-10;
|
@apply text-lg leading-10;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article .toc {
|
#article .toc {
|
||||||
padding-left: 0.8em;
|
padding-left: 0.8em;
|
||||||
@apply my-4;
|
@apply my-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article .toc li {
|
#article .toc li {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article h1 {
|
#article h1 {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@apply text-gray-800 dark:text-gray-200;
|
@apply text-gray-800 dark:text-gray-200;
|
||||||
@apply mt-8 text-5xl font-Barlow;
|
@apply mt-8 text-5xl font-Barlow;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article time {
|
#article time {
|
||||||
display: block;
|
display: block;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@apply text-gray-400 dark:text-gray-600;
|
@apply text-gray-400 dark:text-gray-600;
|
||||||
@apply mt-8 mb-20;
|
@apply mt-8 mb-20;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article h2,
|
#article h2,
|
||||||
@ -120,7 +120,7 @@ h3,
|
|||||||
h4,
|
h4,
|
||||||
h5,
|
h5,
|
||||||
h6 {
|
h6 {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article h2:hover::before,
|
#article h2:hover::before,
|
||||||
@ -128,243 +128,243 @@ h3:hover::before,
|
|||||||
h4:hover::before,
|
h4:hover::before,
|
||||||
h5:hover::before,
|
h5:hover::before,
|
||||||
h6:hover::before {
|
h6:hover::before {
|
||||||
content: '#';
|
content: '#';
|
||||||
left: -1.7rem;
|
left: -1.7rem;
|
||||||
@apply absolute text-gray-400;
|
@apply absolute text-gray-400;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article h2 {
|
#article h2 {
|
||||||
@apply relative text-3xl;
|
@apply relative text-3xl;
|
||||||
@apply text-gray-700 dark:text-gray-200;
|
@apply text-gray-700 dark:text-gray-200;
|
||||||
@apply mt-8 mb-2;
|
@apply mt-8 mb-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article h3 {
|
#article h3 {
|
||||||
@apply relative text-2xl;
|
@apply relative text-2xl;
|
||||||
@apply mt-6 mb-2;
|
@apply mt-6 mb-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article h4 {
|
#article h4 {
|
||||||
@apply relative text-xl;
|
@apply relative text-xl;
|
||||||
@apply mt-6 mb-2;
|
@apply mt-6 mb-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article h5 {
|
#article h5 {
|
||||||
@apply relative;
|
@apply relative;
|
||||||
@apply mt-4 mb-2;
|
@apply mt-4 mb-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article h6 {
|
#article h6 {
|
||||||
@apply relative;
|
@apply relative;
|
||||||
@apply mt-2 mb-2;
|
@apply mt-2 mb-2;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article table {
|
#article table {
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
border-collapse: collapse;
|
border-collapse: collapse;
|
||||||
display: block;
|
display: block;
|
||||||
width: max-content;
|
width: max-content;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article td,
|
#article td,
|
||||||
#article th {
|
#article th {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article details summary {
|
#article details summary {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article table th {
|
#article table th {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article table th,
|
#article table th,
|
||||||
#article table td {
|
#article table td {
|
||||||
padding: 6px 13px;
|
padding: 6px 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article thead tr:first-child {
|
#article thead tr:first-child {
|
||||||
@apply border-t-0;
|
@apply border-t-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article table tr {
|
#article table tr {
|
||||||
background-color: var(--color-canvas-default);
|
background-color: var(--color-canvas-default);
|
||||||
border-top: 1px solid var(--color-border-muted);
|
border-top: 1px solid var(--color-border-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
#article table tr:nth-child(2n) {
|
#article table tr:nth-child(2n) {
|
||||||
background-color: var(--color-canvas-subtle);
|
background-color: var(--color-canvas-subtle);
|
||||||
}
|
}
|
||||||
|
|
||||||
#article table img {
|
#article table img {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article blockquote {
|
#article blockquote {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 1em;
|
padding: 0 1em;
|
||||||
color: var(--color-fg-muted);
|
color: var(--color-fg-muted);
|
||||||
border-left: 0.25em solid var(--color-border-default);
|
border-left: 0.25em solid var(--color-border-default);
|
||||||
}
|
}
|
||||||
|
|
||||||
#article kbd {
|
#article kbd {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 3px 5px;
|
padding: 3px 5px;
|
||||||
font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
|
font: 11px ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas,
|
||||||
Liberation Mono, monospace;
|
Liberation Mono, monospace;
|
||||||
line-height: 10px;
|
line-height: 10px;
|
||||||
color: var(--color-fg-default);
|
color: var(--color-fg-default);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
background-color: var(--color-canvas-subtle);
|
background-color: var(--color-canvas-subtle);
|
||||||
border: solid 1px var(--color-neutral-muted);
|
border: solid 1px var(--color-neutral-muted);
|
||||||
border-bottom-color: var(--color-neutral-muted);
|
border-bottom-color: var(--color-neutral-muted);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: inset 0 -1px 0 var(--color-neutral-muted);
|
box-shadow: inset 0 -1px 0 var(--color-neutral-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
#article hr {
|
#article hr {
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border-bottom: 1px solid var(--color-border-muted);
|
border-bottom: 1px solid var(--color-border-muted);
|
||||||
height: 0.25em;
|
height: 0.25em;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 24px 0;
|
margin: 24px 0;
|
||||||
background-color: var(--color-border-default);
|
background-color: var(--color-border-default);
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article hr::before {
|
#article hr::before {
|
||||||
display: table;
|
display: table;
|
||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
#article hr::after {
|
#article hr::after {
|
||||||
display: table;
|
display: table;
|
||||||
clear: both;
|
clear: both;
|
||||||
content: '';
|
content: '';
|
||||||
}
|
}
|
||||||
|
|
||||||
#article code,
|
#article code,
|
||||||
#article tt {
|
#article tt {
|
||||||
padding: 0.2em 0.4em;
|
padding: 0.2em 0.4em;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 85%;
|
font-size: 85%;
|
||||||
background-color: var(--color-neutral-muted);
|
background-color: var(--color-neutral-muted);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article pre > code {
|
#article pre > code {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
word-break: normal;
|
word-break: normal;
|
||||||
white-space: pre;
|
white-space: pre;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article code,
|
#article code,
|
||||||
#article kbd,
|
#article kbd,
|
||||||
#article pre,
|
#article pre,
|
||||||
#article samp {
|
#article samp {
|
||||||
/* font-family: monospace, monospace; */
|
/* font-family: monospace, monospace; */
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article mark {
|
#article mark {
|
||||||
background-color: var(--color-attention-subtle);
|
background-color: var(--color-attention-subtle);
|
||||||
color: var(--color-text-primary);
|
color: var(--color-text-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
#article sub,
|
#article sub,
|
||||||
#article sup {
|
#article sup {
|
||||||
font-size: 75%;
|
font-size: 75%;
|
||||||
line-height: 0;
|
line-height: 0;
|
||||||
position: relative;
|
position: relative;
|
||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article sub {
|
#article sub {
|
||||||
bottom: -0.25em;
|
bottom: -0.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article sup {
|
#article sup {
|
||||||
top: -0.5em;
|
top: -0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article ol li {
|
#article ol li {
|
||||||
list-style-type: auto;
|
list-style-type: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article ul li {
|
#article ul li {
|
||||||
list-style-type: initial;
|
list-style-type: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article ul.no-list,
|
#article ul.no-list,
|
||||||
#article ol.no-list {
|
#article ol.no-list {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article ol[type='1'] {
|
#article ol[type='1'] {
|
||||||
list-style-type: decimal;
|
list-style-type: decimal;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article ol[type='a'] {
|
#article ol[type='a'] {
|
||||||
list-style-type: lower-alpha;
|
list-style-type: lower-alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article ol[type='i'] {
|
#article ol[type='i'] {
|
||||||
list-style-type: lower-roman;
|
list-style-type: lower-roman;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article div > ol:not([type]) {
|
#article div > ol:not([type]) {
|
||||||
list-style-type: decimal;
|
list-style-type: decimal;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article ul,
|
#article ul,
|
||||||
#article ol {
|
#article ol {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding-left: 2em;
|
padding-left: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article ol ol,
|
#article ol ol,
|
||||||
#article ul ol {
|
#article ul ol {
|
||||||
list-style-type: lower-roman;
|
list-style-type: lower-roman;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article ul ul ol,
|
#article ul ul ol,
|
||||||
#article ul ol ol,
|
#article ul ol ol,
|
||||||
#article ol ul ol,
|
#article ol ul ol,
|
||||||
#article ol ol ol {
|
#article ol ol ol {
|
||||||
list-style-type: lower-alpha;
|
list-style-type: lower-alpha;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article dd {
|
#article dd {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article .sp-layout > .sp-stack {
|
#article .sp-layout > .sp-stack {
|
||||||
height: 400px;
|
height: 400px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
#article .sp-layout > .sp-stack {
|
#article .sp-layout > .sp-stack {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#article img {
|
#article img {
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#article .cm-editor .cm-line {
|
#article .cm-editor .cm-line {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-family: 'JetBrains Mono', -apple-system, monospace;
|
font-family: 'JetBrains Mono', -apple-system, monospace;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user