feat: add toc generation for astro articles. (#63)
This commit is contained in:
parent
eafaef7781
commit
df50e5897b
@ -135,7 +135,7 @@ date: 2013/7/13 20:46:25
|
||||
|
||||
| Setting | Description | Required | Default |
|
||||
|-------------|--------------------------------------|----------|----------------------|
|
||||
| `id` | ID (unique), used as the permalink | true | Filename |
|
||||
| `slug` | ID (unique), used as the permalink | true | Filename |
|
||||
| `title` | Title | true | Filename |
|
||||
| `date` | Published date | true | |
|
||||
| `updated` | Updated date | false | Published date |
|
||||
@ -145,18 +145,21 @@ date: 2013/7/13 20:46:25
|
||||
| `summary` | Post summary in plain text | false | First 140 characters |
|
||||
| `cover` | The cover image | false | `null` |
|
||||
| `published` | Whether the post should be published | false | `true` |
|
||||
| `toc` | Display the Table of Contents | false | `false` |
|
||||
| `alias` | The alternatives slugs for post | false | `[]` |
|
||||
|
||||
### Pages Front Matter Settings
|
||||
|
||||
| Setting | Description | Required | Default |
|
||||
|-------------|--------------------------------------|----------|----------------|
|
||||
| `id` | ID (unique), used as the permalink | true | Filename |
|
||||
| `slug` | ID (unique), used as the permalink | true | Filename |
|
||||
| `title` | Title | true | Filename |
|
||||
| `date` | Published date | true | |
|
||||
| `updated` | Updated date | false | Published date |
|
||||
| `comments` | Enables comment feature for the post | false | `true` |
|
||||
| `cover` | The cover image | false | `null` |
|
||||
| `published` | Whether the post should be published | false | `true` |
|
||||
| `toc` | Display the Table of Contents | false | `false` |
|
||||
|
||||
## Weblog Design
|
||||
|
||||
|
14
options.ts
14
options.ts
@ -74,6 +74,10 @@ const Options = z
|
||||
size: z.number(),
|
||||
}),
|
||||
}),
|
||||
toc: z.object({
|
||||
minHeadingLevel: z.number().optional().default(2),
|
||||
maxHeadingLevel: z.number().optional().default(3),
|
||||
}),
|
||||
}),
|
||||
thumbnail: z
|
||||
.function()
|
||||
@ -92,7 +96,11 @@ const Options = z
|
||||
assetsPrefix,
|
||||
defaultOpenGraph: (): string => `${assetsPrefix()}/images/open-graph.png`,
|
||||
};
|
||||
});
|
||||
})
|
||||
.refine(
|
||||
(options) => options.settings.toc.minHeadingLevel <= options.settings.toc.maxHeadingLevel,
|
||||
'Invalid toc setting, the minHeadingLevel should bellow the maxHeadingLevel',
|
||||
);
|
||||
|
||||
const options: z.input<typeof Options> = {
|
||||
local: {
|
||||
@ -191,6 +199,10 @@ const options: z.input<typeof Options> = {
|
||||
size: 120,
|
||||
},
|
||||
},
|
||||
toc: {
|
||||
minHeadingLevel: 2,
|
||||
maxHeadingLevel: 3,
|
||||
},
|
||||
},
|
||||
thumbnail: ({ src, width, height }) => {
|
||||
if (src.endsWith('.svg')) {
|
||||
|
12
package-lock.json
generated
12
package-lock.json
generated
@ -3896,9 +3896,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.58",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.58.tgz",
|
||||
"integrity": "sha512-al2l4r+24ZFL7WzyPTlyD0fC33LLzvxqLCwurtBibVPghRGO9hSTl+tis8t1kD7biPiH/en4U0I7o/nQbYeoVA==",
|
||||
"version": "1.5.59",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.59.tgz",
|
||||
"integrity": "sha512-faAXB6+gEbC8FsiRdpOXgOe4snP49YwjiXynEB8Mp7sUx80W5eN+BnnBHJ/F7eIeLzs+QBfDD40bJMm97oEFcw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/emmet": {
|
||||
@ -4621,9 +4621,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-raw": {
|
||||
"version": "9.0.4",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz",
|
||||
"integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==",
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz",
|
||||
"integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
|
@ -292,6 +292,15 @@ for (const anchor of document.querySelectorAll('a[href^="#"]')) {
|
||||
});
|
||||
}
|
||||
|
||||
const tocToggle = document.querySelector('.toggle-menu-tree');
|
||||
if (typeof tocToggle !== 'undefined' && tocToggle !== null) {
|
||||
tocToggle.addEventListener('click', () => {
|
||||
const body = document.querySelector('body');
|
||||
const displayToc = !body.classList.contains('display-menu-tree');
|
||||
body.classList.toggle('display-menu-tree', displayToc);
|
||||
});
|
||||
}
|
||||
|
||||
// Add like button for updating likes.
|
||||
const likeButton = document.querySelector('button.post-like');
|
||||
|
||||
|
@ -1424,14 +1424,6 @@ textarea.form-control {
|
||||
padding: 0.75rem 1rem 1rem;
|
||||
}
|
||||
|
||||
.list-bookmarks .list-favicon {
|
||||
background-size: cover;
|
||||
display: inline-block;
|
||||
margin-left: 8px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.list-bookmarks {
|
||||
margin: 2.5rem 0 0;
|
||||
@ -3266,3 +3258,282 @@ blockquote p:last-child {
|
||||
margin: 0 2rem 1.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
/** Table of Contents ------------------------------------ Default */
|
||||
.toggle-menu-tree {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
font-size: 1.375rem;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
margin-right: -5rem;
|
||||
margin-bottom: auto;
|
||||
margin-top: auto;
|
||||
padding-left: 0.35rem;
|
||||
color: #555;
|
||||
width: 6.25rem;
|
||||
height: 6.25rem;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
cursor: pointer;
|
||||
line-height: 6.25rem;
|
||||
border-radius: 6.25rem;
|
||||
border: 0.0625rem solid #f0f0f0;
|
||||
opacity: 1;
|
||||
font-family: -apple-system;
|
||||
z-index: 890;
|
||||
-webkit-transition: 0.5s ease all;
|
||||
-moz-transition: 0.5s ease all;
|
||||
-ms-transition: 0.5s ease all;
|
||||
-o-transition: 0.5s ease all;
|
||||
transition: 0.5s ease all;
|
||||
-webkit-box-shadow: 0 0.125rem 0.3125rem rgba(0, 0, 0, 0.117);
|
||||
-moz-box-shadow: 0 0.125rem 0.3125rem rgba(0, 0, 0, 0.117);
|
||||
box-shadow: 0 0.125rem 0.3125rem rgba(0, 0, 0, 0.117);
|
||||
}
|
||||
|
||||
.toggle-menu-tree i {
|
||||
-webkit-transition: 0.5s ease all;
|
||||
-moz-transition: 0.5s ease all;
|
||||
-ms-transition: 0.5s ease all;
|
||||
-o-transition: 0.5s ease all;
|
||||
transition: 0.5s ease all;
|
||||
}
|
||||
|
||||
.toggle-menu-tree:hover {
|
||||
background: #fafafa;
|
||||
-webkit-transform: translateX(-1.25rem);
|
||||
-moz-transform: translateX(-1.25rem);
|
||||
-ms-transform: translateX(-1.25rem);
|
||||
-o-transform: translateX(-1.25rem);
|
||||
transform: translateX(-1.25rem);
|
||||
}
|
||||
|
||||
.post-menu {
|
||||
position: fixed;
|
||||
display: table;
|
||||
top: 0;
|
||||
right: -18.125rem;
|
||||
bottom: 0;
|
||||
width: 17.5rem;
|
||||
height: 100%;
|
||||
background-color: #fafafa;
|
||||
border-left: 0.0625rem solid #f0f0f0;
|
||||
opacity: 1;
|
||||
z-index: 880;
|
||||
font-weight: 400;
|
||||
-webkit-transition: 0.5s ease all;
|
||||
-moz-transition: 0.5s ease all;
|
||||
-ms-transition: 0.5s ease all;
|
||||
-o-transition: 0.5s ease all;
|
||||
transition: 0.5s ease all;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-button {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-button:start:decrement,
|
||||
.post-menu::-webkit-scrollbar-button:end:increment {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-button:vertical:start:increment,
|
||||
.post-menu::-webkit-scrollbar-button:vertical:end:decrement {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-track:vertical,
|
||||
.post-menu::-webkit-scrollbar-track:horizontal {
|
||||
background-clip: padding-box;
|
||||
background-color: #191919;
|
||||
border: 0 solid transparent;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-track:hover {
|
||||
background-color: #191919;
|
||||
-webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-track:active {
|
||||
background-color: #191919;
|
||||
-webkit-box-shadow:
|
||||
inset 1px 0 0 rgba(0, 0, 0, 0.14),
|
||||
inset -1px -1px 0 rgba(0, 0, 0, 0.07);
|
||||
-moz-box-shadow:
|
||||
inset 1px 0 0 rgba(0, 0, 0, 0.14),
|
||||
inset -1px -1px 0 rgba(0, 0, 0, 0.07);
|
||||
box-shadow:
|
||||
inset 1px 0 0 rgba(0, 0, 0, 0.14),
|
||||
inset -1px -1px 0 rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-thumb {
|
||||
background-clip: padding-box;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
min-height: 40px;
|
||||
padding-top: 100px;
|
||||
border-radius: 2px;
|
||||
-webkit-box-shadow:
|
||||
inset 1px 1px 0 rgba(0, 0, 0, 0.1),
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.07);
|
||||
-moz-box-shadow:
|
||||
inset 1px 1px 0 rgba(0, 0, 0, 0.1),
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.07);
|
||||
box-shadow:
|
||||
inset 1px 1px 0 rgba(0, 0, 0, 0.1),
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
-webkit-box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.25);
|
||||
-moz-box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-thumb:active {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
-webkit-box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.35);
|
||||
-moz-box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.35);
|
||||
box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-thumb:vertical,
|
||||
.post-menu::-webkit-scrollbar-thumb:horizontal {
|
||||
border: 0 solid transparent;
|
||||
}
|
||||
|
||||
.toc-wrap {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: -3rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
--webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.toc-content {
|
||||
margin-right: 3rem;
|
||||
padding-top: 2.875rem;
|
||||
-webkit-transition: 0.5s ease all;
|
||||
-moz-transition: 0.5s ease all;
|
||||
-ms-transition: 0.5s ease all;
|
||||
-o-transition: 0.5s ease all;
|
||||
transition: 0.5s ease all;
|
||||
}
|
||||
|
||||
.post-menu-title {
|
||||
font-size: 1.5rem;
|
||||
text-align: left;
|
||||
line-height: 3.6rem;
|
||||
width: 100%;
|
||||
font-weight: 700;
|
||||
padding: 0 2.5rem;
|
||||
color: #202020;
|
||||
}
|
||||
|
||||
.index-menu {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.index-menu-list {
|
||||
line-height: 1.8em;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.index-menu-item {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.index-menu-item > .index-menu-list span.menu-content {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.index-menu-item > .index-menu-list > .index-menu-item > .index-menu-list span.menu-content {
|
||||
padding-left: 4rem;
|
||||
}
|
||||
|
||||
.index-menu-item.current > a.index-menu-link {
|
||||
background: #f5f5f5;
|
||||
color: #1abc9c;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
a.index-menu-link {
|
||||
color: #555;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.375rem 2.5rem;
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
a.index-menu-link:hover {
|
||||
background: #efefef;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.post-menu-overlay {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/** Table of Contents ------------------------------------ Display */
|
||||
.display-menu-tree .post-menu {
|
||||
-webkit-transform: translateX(-18.125rem);
|
||||
-moz-transform: translateX(-18.125rem);
|
||||
-ms-transform: translateX(-18.125rem);
|
||||
-o-transform: translateX(-18.125rem);
|
||||
transform: translateX(-18.125rem);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.display-menu-tree .toggle-menu-tree {
|
||||
background: #fafafa;
|
||||
padding-left: 0;
|
||||
width: 3.125rem;
|
||||
height: 3.125rem;
|
||||
line-height: 3.125rem;
|
||||
text-align: center;
|
||||
margin-right: -1.5625rem;
|
||||
-webkit-transform: translateX(-17.5rem);
|
||||
-moz-transform: translateX(-17.5rem);
|
||||
-ms-transform: translateX(-17.5rem);
|
||||
-o-transform: translateX(-17.5rem);
|
||||
transform: translateX(-17.5rem);
|
||||
z-index: 1500;
|
||||
}
|
||||
|
||||
.display-menu-tree .toggle-menu-tree i {
|
||||
-webkit-transform: rotate(180deg);
|
||||
-moz-transform: rotate(180deg);
|
||||
-ms-transform: rotate(180deg);
|
||||
-o-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.display-menu-tree .post-menu-overlay {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(8, 15, 25, 0.3);
|
||||
visibility: visible;
|
||||
pointer-events: initial;
|
||||
z-index: 500;
|
||||
}
|
||||
|
3566
src/assets/styles/iconfont/iconfont.html
Normal file
3566
src/assets/styles/iconfont/iconfont.html
Normal file
File diff suppressed because it is too large
Load Diff
271
src/assets/styles/toc.css
Normal file
271
src/assets/styles/toc.css
Normal file
@ -0,0 +1,271 @@
|
||||
/* Table of Contents ----------------------------------- */
|
||||
|
||||
.post-menu {
|
||||
position: fixed;
|
||||
display: table;
|
||||
top: 0;
|
||||
right: -18.125rem;
|
||||
bottom: 0;
|
||||
width: 17.5rem;
|
||||
height: 100%;
|
||||
background-color: #fafafa;
|
||||
border-left: 0.0625rem solid #f0f0f0;
|
||||
opacity: 1;
|
||||
z-index: 880;
|
||||
font-weight: 400;
|
||||
-webkit-transition: 0.5s ease all;
|
||||
-moz-transition: 0.5s ease all;
|
||||
-ms-transition: 0.5s ease all;
|
||||
-o-transition: 0.5s ease all;
|
||||
transition: 0.5s ease all;
|
||||
}
|
||||
|
||||
.display-menu-tree .post-menu {
|
||||
-webkit-transform: translateX(-18.125rem);
|
||||
-moz-transform: translateX(-18.125rem);
|
||||
-ms-transform: translateX(-18.125rem);
|
||||
-o-transform: translateX(-18.125rem);
|
||||
transform: translateX(-18.125rem);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-button {
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-button:start:decrement,
|
||||
.post-menu::-webkit-scrollbar-button:end:increment {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-button:vertical:start:increment,
|
||||
.post-menu::-webkit-scrollbar-button:vertical:end:decrement {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-track:vertical,
|
||||
.post-menu::-webkit-scrollbar-track:horizontal {
|
||||
background-clip: padding-box;
|
||||
background-color: #191919;
|
||||
border: 0 solid transparent;
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-track:hover {
|
||||
background-color: #191919;
|
||||
-webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
-moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-track:active {
|
||||
background-color: #191919;
|
||||
-webkit-box-shadow:
|
||||
inset 1px 0 0 rgba(0, 0, 0, 0.14),
|
||||
inset -1px -1px 0 rgba(0, 0, 0, 0.07);
|
||||
-moz-box-shadow:
|
||||
inset 1px 0 0 rgba(0, 0, 0, 0.14),
|
||||
inset -1px -1px 0 rgba(0, 0, 0, 0.07);
|
||||
box-shadow:
|
||||
inset 1px 0 0 rgba(0, 0, 0, 0.14),
|
||||
inset -1px -1px 0 rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-thumb {
|
||||
background-clip: padding-box;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
min-height: 40px;
|
||||
padding-top: 100px;
|
||||
border-radius: 2px;
|
||||
-webkit-box-shadow:
|
||||
inset 1px 1px 0 rgba(0, 0, 0, 0.1),
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.07);
|
||||
-moz-box-shadow:
|
||||
inset 1px 1px 0 rgba(0, 0, 0, 0.1),
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.07);
|
||||
box-shadow:
|
||||
inset 1px 1px 0 rgba(0, 0, 0, 0.1),
|
||||
inset 0 -1px 0 rgba(0, 0, 0, 0.07);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgba(255, 255, 255, 0.4);
|
||||
-webkit-box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.25);
|
||||
-moz-box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.25);
|
||||
box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-thumb:active {
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
-webkit-box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.35);
|
||||
-moz-box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.35);
|
||||
box-shadow: inset 1px 1px 3px rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.post-menu::-webkit-scrollbar-thumb:vertical,
|
||||
.post-menu::-webkit-scrollbar-thumb:horizontal {
|
||||
border: 0 solid transparent;
|
||||
}
|
||||
|
||||
.toggle-menu-tree {
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
font-size: 1.375rem;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
margin-right: -5rem;
|
||||
margin-bottom: auto;
|
||||
margin-top: auto;
|
||||
padding-left: 0.35rem;
|
||||
color: #555;
|
||||
width: 6.25rem;
|
||||
height: 6.25rem;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
cursor: pointer;
|
||||
line-height: 6.25rem;
|
||||
border-radius: 6.25rem;
|
||||
border: 0.0625rem solid #f0f0f0;
|
||||
opacity: 1;
|
||||
font-family: -apple-system;
|
||||
z-index: 890;
|
||||
-webkit-transition: 0.5s ease all;
|
||||
-moz-transition: 0.5s ease all;
|
||||
-ms-transition: 0.5s ease all;
|
||||
-o-transition: 0.5s ease all;
|
||||
transition: 0.5s ease all;
|
||||
-webkit-box-shadow: 0 0.125rem 0.3125rem rgba(0, 0, 0, 0.117);
|
||||
-moz-box-shadow: 0 0.125rem 0.3125rem rgba(0, 0, 0, 0.117);
|
||||
box-shadow: 0 0.125rem 0.3125rem rgba(0, 0, 0, 0.117);
|
||||
}
|
||||
|
||||
.display-menu-tree .toggle-menu-tree {
|
||||
background: #fafafa;
|
||||
padding-left: 0;
|
||||
width: 3.125rem;
|
||||
height: 3.125rem;
|
||||
line-height: 3.125rem;
|
||||
text-align: center;
|
||||
margin-right: -1.5625rem;
|
||||
-webkit-transform: translateX(-17.5rem);
|
||||
-moz-transform: translateX(-17.5rem);
|
||||
-ms-transform: translateX(-17.5rem);
|
||||
-o-transform: translateX(-17.5rem);
|
||||
transform: translateX(-17.5rem);
|
||||
}
|
||||
|
||||
.toggle-menu-tree i {
|
||||
-webkit-transition: 0.5s ease all;
|
||||
-moz-transition: 0.5s ease all;
|
||||
-ms-transition: 0.5s ease all;
|
||||
-o-transition: 0.5s ease all;
|
||||
transition: 0.5s ease all;
|
||||
}
|
||||
|
||||
.display-menu-tree .toggle-menu-tree i {
|
||||
-webkit-transform: rotate(180deg);
|
||||
-moz-transform: rotate(180deg);
|
||||
-ms-transform: rotate(180deg);
|
||||
-o-transform: rotate(180deg);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.toggle-menu-tree:hover {
|
||||
background: #fafafa;
|
||||
-webkit-transform: translateX(-1.25rem);
|
||||
-moz-transform: translateX(-1.25rem);
|
||||
-ms-transform: translateX(-1.25rem);
|
||||
-o-transform: translateX(-1.25rem);
|
||||
transform: translateX(-1.25rem);
|
||||
}
|
||||
|
||||
.toggle-menu-tree.show {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.toggle-menu-tree.hide {
|
||||
right: -5.3125rem;
|
||||
}
|
||||
|
||||
.display-menu-tree .toggle-menu-tree.hide {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.toc-wrap {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: -3rem;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
--webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.toc-content {
|
||||
margin-right: 3rem;
|
||||
padding-top: 2.875rem;
|
||||
-webkit-transition: 0.5s ease all;
|
||||
-moz-transition: 0.5s ease all;
|
||||
-ms-transition: 0.5s ease all;
|
||||
-o-transition: 0.5s ease all;
|
||||
transition: 0.5s ease all;
|
||||
}
|
||||
|
||||
.post-menu-title {
|
||||
font-size: 1.5rem;
|
||||
text-align: left;
|
||||
line-height: 3.6rem;
|
||||
width: 100%;
|
||||
font-weight: 700;
|
||||
padding: 0 2.5rem;
|
||||
color: #202020;
|
||||
}
|
||||
|
||||
.index-menu {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.index-menu-list {
|
||||
line-height: 1.8em;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.index-menu-item {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.index-menu-item > .index-menu-list span.menu-content {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.index-menu-item > .index-menu-list > .index-menu-item > .index-menu-list span.menu-content {
|
||||
padding-left: 4rem;
|
||||
}
|
||||
|
||||
.index-menu-item.current > a.index-menu-link {
|
||||
background: #f5f5f5;
|
||||
color: #1abc9c;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
a.index-menu-link {
|
||||
color: #555;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 0.85rem;
|
||||
padding: 0.375rem 2.5rem;
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
a.index-menu-link:hover {
|
||||
background: #efefef;
|
||||
color: #333;
|
||||
}
|
@ -26,15 +26,7 @@ const list = _.shuffle(friends);
|
||||
</div>
|
||||
<div class="list-content">
|
||||
<div class="list-body">
|
||||
<div class="list-title h6 h-1x">
|
||||
{friend.website}
|
||||
<div
|
||||
class="list-favicon"
|
||||
style={{
|
||||
backgroundImage: `url('${friend.favicon}')`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div class="list-title h6 h-1x">{friend.website}</div>
|
||||
<div class="text-sm text-secondary h-2x mt-1">{friend.description ? friend.description : ' '}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
34
src/components/page/toc/TableOfContents.astro
Normal file
34
src/components/page/toc/TableOfContents.astro
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
import TocItems from '@/components/page/toc/TocItems.astro';
|
||||
import { generateToC, type TocOpts } from '@/helpers/toc';
|
||||
import type { MarkdownHeading } from 'astro';
|
||||
|
||||
interface Props {
|
||||
headings: MarkdownHeading[];
|
||||
toc: TocOpts | false;
|
||||
}
|
||||
|
||||
const { headings, toc } = Astro.props;
|
||||
const items = generateToC(headings, toc);
|
||||
---
|
||||
|
||||
{
|
||||
items.length > 0 && (
|
||||
<Fragment>
|
||||
<a class="toggle-menu-tree">
|
||||
<i class="text-md iconfont icon-left" />
|
||||
</a>
|
||||
<div class="post-menu">
|
||||
<div class="toc-wrap">
|
||||
<div class="toc-content">
|
||||
<h2 class="post-menu-title">文章目录</h2>
|
||||
<div class="index-menu">
|
||||
<TocItems {items} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="post-menu-overlay" />
|
||||
</Fragment>
|
||||
)
|
||||
}
|
22
src/components/page/toc/TocItems.astro
Normal file
22
src/components/page/toc/TocItems.astro
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
import type { TocItem } from '@/helpers/toc';
|
||||
|
||||
interface Props {
|
||||
items: TocItem[];
|
||||
}
|
||||
|
||||
const { items } = Astro.props;
|
||||
---
|
||||
|
||||
<ul class="index-menu-list">
|
||||
{
|
||||
items.map((item) => (
|
||||
<li class="index-menu-item">
|
||||
<a data-scroll class="index-menu-link" href={`#${item.slug}`} title={item.text}>
|
||||
<span class="menu-content">{item.text}</span>
|
||||
</a>
|
||||
{item.children.length > 0 && <Astro.self items={item.children} />}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
@ -22,6 +22,30 @@ const image = (fallbackImage: string) =>
|
||||
.default(fallbackImage)
|
||||
.transform((file) => imageMetadata(file));
|
||||
|
||||
// The default toc heading level.
|
||||
const toc = () =>
|
||||
z
|
||||
.union([
|
||||
z.object({
|
||||
// The level to start including headings at in the table of contents. Default: 2.
|
||||
minHeadingLevel: z.number().int().min(1).max(6).optional().default(options.settings.toc.minHeadingLevel),
|
||||
// The level to stop including headings at in the table of contents. Default: 3.
|
||||
maxHeadingLevel: z.number().int().min(1).max(6).optional().default(options.settings.toc.maxHeadingLevel),
|
||||
}),
|
||||
z.boolean().transform((enabled) =>
|
||||
enabled
|
||||
? {
|
||||
minHeadingLevel: options.settings.toc.minHeadingLevel,
|
||||
maxHeadingLevel: options.settings.toc.maxHeadingLevel,
|
||||
}
|
||||
: false,
|
||||
),
|
||||
])
|
||||
.default(false)
|
||||
.refine((toc) => (toc ? toc.minHeadingLevel <= toc.maxHeadingLevel : true), {
|
||||
message: 'minHeadingLevel must be less than or equal to maxHeadingLevel',
|
||||
});
|
||||
|
||||
// Categories Collection
|
||||
const categoriesCollection = defineCollection({
|
||||
type: 'data',
|
||||
@ -70,6 +94,7 @@ const postsCollection = defineCollection({
|
||||
summary: z.string().optional().default(''),
|
||||
cover: image(defaultCover),
|
||||
published: z.boolean().optional().default(true),
|
||||
toc: toc(),
|
||||
}),
|
||||
});
|
||||
|
||||
@ -84,6 +109,7 @@ const pagesCollection = defineCollection({
|
||||
cover: image(defaultCover),
|
||||
published: z.boolean().optional().default(true),
|
||||
friend: z.boolean().optional().default(false),
|
||||
toc: toc(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -6,7 +6,6 @@
|
||||
description: Trying to light up the dark
|
||||
homepage: https://cynosura.one
|
||||
poster: /images/links/cynosura.one.jpg
|
||||
favicon: https://cynosura.one/img/favicon.ico
|
||||
- website: EEE.ME
|
||||
description: 途方に暮れて
|
||||
homepage: https://eee.me
|
||||
@ -22,7 +21,6 @@
|
||||
description: 这世界需要更多英雄
|
||||
homepage: https://blog.mboker.cn
|
||||
poster: /images/links/mboker.cn.jpg
|
||||
favicon: data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text x=%22-0.125em%22 y=%22.9em%22 font-size=%2290%22>🌻</text></svg>
|
||||
- website: 品味苏州
|
||||
description: 吴侬软语、小桥流水的生活点滴
|
||||
homepage: https://pwsz.com
|
||||
@ -42,12 +40,11 @@
|
||||
description: 茶盐观瑟丶行者自若
|
||||
homepage: https://alexinea.com
|
||||
poster: /images/links/alexinea.com.jpg
|
||||
favicon: https://alexinea.com/wp-content/uploads/2016/09/cropped-20160812.2-32x32.png
|
||||
- website: 贾顺名
|
||||
description: 全沾码农的一些笔记
|
||||
homepage: https://jiasm.github.io
|
||||
poster: /images/links/jiasm.github.io.jpg
|
||||
- website: 白丁轶事 - FMoran的自留地
|
||||
- website: 白丁轶事
|
||||
description: Everything should be hurry.
|
||||
homepage: https://fmoran.me
|
||||
poster: /images/links/fmoran.me.jpg
|
||||
@ -55,22 +52,18 @@
|
||||
description: 船长の部落格,记录有趣的事,分享技术经验。
|
||||
homepage: https://captainofphb.me
|
||||
poster: /images/links/captainofphb.me.jpg
|
||||
favicon: https://captainofphb.me/favicon.svg
|
||||
- website: 小萌博客
|
||||
description: 互联网分享笔记
|
||||
homepage: https://blog.luoli.net
|
||||
poster: /images/links/luoli.net.jpg
|
||||
favicon: https://blog.luoli.net/usr/themes/MUltraMoe/images/favicon.png
|
||||
- website: LongLuo's Life Notes
|
||||
description: 每一天都是奇迹
|
||||
homepage: https://www.longluo.me
|
||||
poster: /images/links/longluo.me.jpg
|
||||
favicon: https://www.longluo.me/assets/logo/favicon-32x32.png
|
||||
- website: 佛爷小窝
|
||||
description: 一个基于内容分享,创作与灵感结合的折腾笔记博客。
|
||||
homepage: https://www.lafoyer.com
|
||||
poster: /images/links/lafoyer.com.jpg
|
||||
favicon: https://www.boxmoe.com/file/100logo.png
|
||||
- website: 白熊阿丸的小屋
|
||||
description: 在这里可以看到一个真实的我,我会在这里书写我的一切
|
||||
homepage: https://bxaw.name
|
||||
|
@ -7,6 +7,9 @@ tags:
|
||||
- 博客
|
||||
category: 编程
|
||||
summary: 如你所见,当你访问这篇文章的时候,我已经把写了 13 年的博客程序从 WordPress 迁移到了自己用 Next.js 写的程序,这可能是我第 N 次尝试使用其他的方式写博客,但我想绝对不会是最后一次。
|
||||
toc:
|
||||
minHeadingLevel: 2
|
||||
maxHeadingLevel: 3
|
||||
cover: /images/2024/04/2024040719182310.jpg
|
||||
---
|
||||
|
||||
|
@ -8,6 +8,7 @@ tags:
|
||||
- 期房
|
||||
category: 杂谈
|
||||
summary: 这篇文章试图从金融视角来期房销售的问题,也希望能回答相关投资理财遇到资损的问题。
|
||||
toc: true
|
||||
cover: /images/2024/06/2024063015135000.jpg
|
||||
---
|
||||
|
||||
|
@ -7,6 +7,7 @@ tags:
|
||||
- 学习
|
||||
category: 杂谈
|
||||
summary: 于我们成年人而言,虽已不能像儿童一样掌握一门外语,但是我们可通过“使用”的方式来习得英语。
|
||||
toc: true
|
||||
cover: /images/2024/10/2024101621070600.jpg
|
||||
---
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { defaultCover } from '@/content/config.ts';
|
||||
import options from '@/options';
|
||||
import { getCollection, getEntry, type Render } from 'astro:content';
|
||||
import { getCollection, type Render } from 'astro:content';
|
||||
|
||||
// Import the collections from the astro content.
|
||||
const categoriesCollection = await getCollection('categories');
|
||||
@ -24,7 +24,6 @@ export type Post = (typeof postsCollection)[number]['data'] & {
|
||||
slug: string;
|
||||
permalink: string;
|
||||
render: () => Render['.mdx'];
|
||||
raw: () => Promise<string>;
|
||||
};
|
||||
export type Tag = (typeof tagsCollection)[number]['data'][number] & { counts: number; permalink: string };
|
||||
|
||||
@ -36,10 +35,7 @@ export const pages: Page[] = pagesCollection
|
||||
.map((page) => ({
|
||||
slug: page.slug,
|
||||
permalink: `/${page.slug}`,
|
||||
render: async () => {
|
||||
const entry = await getEntry('pages', page.slug);
|
||||
return entry.render();
|
||||
},
|
||||
render: page.render,
|
||||
...page.data,
|
||||
}));
|
||||
export const posts: Post[] = postsCollection
|
||||
@ -47,14 +43,7 @@ export const posts: Post[] = postsCollection
|
||||
.map((post) => ({
|
||||
slug: post.slug,
|
||||
permalink: `/posts/${post.slug}`,
|
||||
render: async () => {
|
||||
const entry = await getEntry('posts', post.slug);
|
||||
return entry.render();
|
||||
},
|
||||
raw: async () => {
|
||||
const entry = await getEntry('posts', post.slug);
|
||||
return entry.body;
|
||||
},
|
||||
render: post.render,
|
||||
...post.data,
|
||||
}))
|
||||
.sort((left: Post, right: Post) => {
|
||||
|
34
src/helpers/toc.ts
Normal file
34
src/helpers/toc.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import type { MarkdownHeading } from 'astro';
|
||||
|
||||
export interface TocItem extends MarkdownHeading {
|
||||
children: TocItem[];
|
||||
}
|
||||
|
||||
export interface TocOpts {
|
||||
minHeadingLevel: number;
|
||||
maxHeadingLevel: number;
|
||||
}
|
||||
|
||||
// Convert the flat headings array generated by Astro into a nested tree structure.
|
||||
export function generateToC(headings: MarkdownHeading[], opts: TocOpts | false): TocItem[] {
|
||||
if (opts === false) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { minHeadingLevel, maxHeadingLevel } = opts;
|
||||
const toc: Array<TocItem> = [];
|
||||
for (const heading of headings.filter(({ depth }) => depth >= minHeadingLevel && depth <= maxHeadingLevel)) {
|
||||
injectChild(toc, { ...heading, children: [] });
|
||||
}
|
||||
return toc;
|
||||
}
|
||||
|
||||
// Inject a ToC entry as deep in the tree as its `depth` property requires.
|
||||
function injectChild(items: TocItem[], item: TocItem): void {
|
||||
const lastItem = items.at(-1);
|
||||
if (!lastItem || lastItem.depth >= item.depth) {
|
||||
items.push(item);
|
||||
} else {
|
||||
injectChild(lastItem.children, item);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import Image from '@/components/image/Image.astro';
|
||||
import LikeButton from '@/components/like/LikeButton.astro';
|
||||
import PageMeta from '@/components/meta/PageMeta.astro';
|
||||
import Friends from '@/components/page/friend/Friends.astro';
|
||||
import TableOfContents from '@/components/page/toc/TableOfContents.astro';
|
||||
import MusicPlayer from '@/components/player/MusicPlayer.astro';
|
||||
import type { Page } from '@/helpers/schema';
|
||||
import { urlJoin } from '@/helpers/tools';
|
||||
@ -15,7 +16,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const { page } = Astro.props;
|
||||
const { Content } = await page.render();
|
||||
const { Content, headings } = await page.render();
|
||||
---
|
||||
|
||||
<BaseLayout title={page.title}>
|
||||
@ -25,6 +26,7 @@ const { Content } = await page.render();
|
||||
<div class="container">
|
||||
<div class="post">
|
||||
<h1 class="post-title mb-3 mb-xl-4">{page.title}</h1>
|
||||
<TableOfContents {headings} toc={page.toc} />
|
||||
<div class="post-content">
|
||||
<div class="nc-light-gallery">
|
||||
<Content components={{ MusicPlayer: MusicPlayer, Image: Image }} />
|
||||
|
@ -4,6 +4,7 @@ import Image from '@/components/image/Image.astro';
|
||||
import LikeButton from '@/components/like/LikeButton.astro';
|
||||
import LikeShare from '@/components/like/LikeShare.astro';
|
||||
import PostMeta from '@/components/meta/PostMeta.astro';
|
||||
import TableOfContents from '@/components/page/toc/TableOfContents.astro';
|
||||
import MusicPlayer from '@/components/player/MusicPlayer.astro';
|
||||
import Sidebar from '@/components/sidebar/Sidebar.astro';
|
||||
import { formatShowDate } from '@/helpers/formatter';
|
||||
@ -17,7 +18,7 @@ interface Props {
|
||||
}
|
||||
|
||||
const { post } = Astro.props;
|
||||
const { Content } = await post.render();
|
||||
const { Content, headings } = await post.render();
|
||||
---
|
||||
|
||||
<BaseLayout title={post.title}>
|
||||
@ -53,6 +54,7 @@ const { Content } = await post.render();
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<TableOfContents {headings} toc={post.toc} />
|
||||
<div class="post-content">
|
||||
<div class="nc-light-gallery">
|
||||
<Content components={{ MusicPlayer: MusicPlayer, Image: Image }} />
|
||||
|
Loading…
Reference in New Issue
Block a user