Compare commits
3 Commits
99dd5dc5d1
...
0ea993adc5
Author | SHA1 | Date | |
---|---|---|---|
0ea993adc5 | |||
fb9bf2dc72 | |||
efbaffeb2d |
@ -135,7 +135,7 @@ date: 2013/7/13 20:46:25
|
|||||||
|
|
||||||
| Setting | Description | Required | Default |
|
| 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 |
|
| `title` | Title | true | Filename |
|
||||||
| `date` | Published date | true | |
|
| `date` | Published date | true | |
|
||||||
| `updated` | Updated date | false | Published date |
|
| `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 |
|
| `summary` | Post summary in plain text | false | First 140 characters |
|
||||||
| `cover` | The cover image | false | `null` |
|
| `cover` | The cover image | false | `null` |
|
||||||
| `published` | Whether the post should be published | false | `true` |
|
| `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
|
### Pages Front Matter Settings
|
||||||
|
|
||||||
| Setting | Description | Required | Default |
|
| 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 |
|
| `title` | Title | true | Filename |
|
||||||
| `date` | Published date | true | |
|
| `date` | Published date | true | |
|
||||||
| `updated` | Updated date | false | Published date |
|
| `updated` | Updated date | false | Published date |
|
||||||
| `comments` | Enables comment feature for the post | false | `true` |
|
| `comments` | Enables comment feature for the post | false | `true` |
|
||||||
| `cover` | The cover image | false | `null` |
|
| `cover` | The cover image | false | `null` |
|
||||||
| `published` | Whether the post should be published | false | `true` |
|
| `published` | Whether the post should be published | false | `true` |
|
||||||
|
| `toc` | Display the Table of Contents | false | `false` |
|
||||||
|
|
||||||
## Weblog Design
|
## Weblog Design
|
||||||
|
|
||||||
|
14
options.ts
14
options.ts
@ -74,6 +74,10 @@ const Options = z
|
|||||||
size: z.number(),
|
size: z.number(),
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
toc: z.object({
|
||||||
|
minHeadingLevel: z.number().optional().default(2),
|
||||||
|
maxHeadingLevel: z.number().optional().default(3),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
thumbnail: z
|
thumbnail: z
|
||||||
.function()
|
.function()
|
||||||
@ -92,7 +96,11 @@ const Options = z
|
|||||||
assetsPrefix,
|
assetsPrefix,
|
||||||
defaultOpenGraph: (): string => `${assetsPrefix()}/images/open-graph.png`,
|
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> = {
|
const options: z.input<typeof Options> = {
|
||||||
local: {
|
local: {
|
||||||
@ -191,6 +199,10 @@ const options: z.input<typeof Options> = {
|
|||||||
size: 120,
|
size: 120,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
toc: {
|
||||||
|
minHeadingLevel: 2,
|
||||||
|
maxHeadingLevel: 3,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
thumbnail: ({ src, width, height }) => {
|
thumbnail: ({ src, width, height }) => {
|
||||||
if (src.endsWith('.svg')) {
|
if (src.endsWith('.svg')) {
|
||||||
|
12
package-lock.json
generated
12
package-lock.json
generated
@ -3896,9 +3896,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.5.58",
|
"version": "1.5.59",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.58.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.59.tgz",
|
||||||
"integrity": "sha512-al2l4r+24ZFL7WzyPTlyD0fC33LLzvxqLCwurtBibVPghRGO9hSTl+tis8t1kD7biPiH/en4U0I7o/nQbYeoVA==",
|
"integrity": "sha512-faAXB6+gEbC8FsiRdpOXgOe4snP49YwjiXynEB8Mp7sUx80W5eN+BnnBHJ/F7eIeLzs+QBfDD40bJMm97oEFcw==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/emmet": {
|
"node_modules/emmet": {
|
||||||
@ -4621,9 +4621,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hast-util-raw": {
|
"node_modules/hast-util-raw": {
|
||||||
"version": "9.0.4",
|
"version": "9.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz",
|
||||||
"integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==",
|
"integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/hast": "^3.0.0",
|
"@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.
|
// Add like button for updating likes.
|
||||||
const likeButton = document.querySelector('button.post-like');
|
const likeButton = document.querySelector('button.post-like');
|
||||||
|
|
||||||
|
@ -1424,14 +1424,6 @@ textarea.form-control {
|
|||||||
padding: 0.75rem 1rem 1rem;
|
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) {
|
@media (max-width: 767.98px) {
|
||||||
.list-bookmarks {
|
.list-bookmarks {
|
||||||
margin: 2.5rem 0 0;
|
margin: 2.5rem 0 0;
|
||||||
@ -3266,3 +3258,282 @@ blockquote p:last-child {
|
|||||||
margin: 0 2rem 1.25rem;
|
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
@ -26,15 +26,7 @@ const list = _.shuffle(friends);
|
|||||||
</div>
|
</div>
|
||||||
<div class="list-content">
|
<div class="list-content">
|
||||||
<div class="list-body">
|
<div class="list-body">
|
||||||
<div class="list-title h6 h-1x">
|
<div class="list-title h6 h-1x">{friend.website}</div>
|
||||||
{friend.website}
|
|
||||||
<div
|
|
||||||
class="list-favicon"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url('${friend.favicon}')`,
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="text-sm text-secondary h-2x mt-1">{friend.description ? friend.description : ' '}</div>
|
<div class="text-sm text-secondary h-2x mt-1">{friend.description ? friend.description : ' '}</div>
|
||||||
</div>
|
</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)
|
.default(fallbackImage)
|
||||||
.transform((file) => imageMetadata(file));
|
.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
|
// Categories Collection
|
||||||
const categoriesCollection = defineCollection({
|
const categoriesCollection = defineCollection({
|
||||||
type: 'data',
|
type: 'data',
|
||||||
@ -70,6 +94,7 @@ const postsCollection = defineCollection({
|
|||||||
summary: z.string().optional().default(''),
|
summary: z.string().optional().default(''),
|
||||||
cover: image(defaultCover),
|
cover: image(defaultCover),
|
||||||
published: z.boolean().optional().default(true),
|
published: z.boolean().optional().default(true),
|
||||||
|
toc: toc(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -84,6 +109,7 @@ const pagesCollection = defineCollection({
|
|||||||
cover: image(defaultCover),
|
cover: image(defaultCover),
|
||||||
published: z.boolean().optional().default(true),
|
published: z.boolean().optional().default(true),
|
||||||
friend: z.boolean().optional().default(false),
|
friend: z.boolean().optional().default(false),
|
||||||
|
toc: toc(),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
description: Trying to light up the dark
|
description: Trying to light up the dark
|
||||||
homepage: https://cynosura.one
|
homepage: https://cynosura.one
|
||||||
poster: /images/links/cynosura.one.jpg
|
poster: /images/links/cynosura.one.jpg
|
||||||
favicon: https://cynosura.one/img/favicon.ico
|
|
||||||
- website: EEE.ME
|
- website: EEE.ME
|
||||||
description: 途方に暮れて
|
description: 途方に暮れて
|
||||||
homepage: https://eee.me
|
homepage: https://eee.me
|
||||||
@ -22,7 +21,6 @@
|
|||||||
description: 这世界需要更多英雄
|
description: 这世界需要更多英雄
|
||||||
homepage: https://blog.mboker.cn
|
homepage: https://blog.mboker.cn
|
||||||
poster: /images/links/mboker.cn.jpg
|
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: 品味苏州
|
- website: 品味苏州
|
||||||
description: 吴侬软语、小桥流水的生活点滴
|
description: 吴侬软语、小桥流水的生活点滴
|
||||||
homepage: https://pwsz.com
|
homepage: https://pwsz.com
|
||||||
@ -42,12 +40,11 @@
|
|||||||
description: 茶盐观瑟丶行者自若
|
description: 茶盐观瑟丶行者自若
|
||||||
homepage: https://alexinea.com
|
homepage: https://alexinea.com
|
||||||
poster: /images/links/alexinea.com.jpg
|
poster: /images/links/alexinea.com.jpg
|
||||||
favicon: https://alexinea.com/wp-content/uploads/2016/09/cropped-20160812.2-32x32.png
|
|
||||||
- website: 贾顺名
|
- website: 贾顺名
|
||||||
description: 全沾码农的一些笔记
|
description: 全沾码农的一些笔记
|
||||||
homepage: https://jiasm.github.io
|
homepage: https://jiasm.github.io
|
||||||
poster: /images/links/jiasm.github.io.jpg
|
poster: /images/links/jiasm.github.io.jpg
|
||||||
- website: 白丁轶事 - FMoran的自留地
|
- website: 白丁轶事
|
||||||
description: Everything should be hurry.
|
description: Everything should be hurry.
|
||||||
homepage: https://fmoran.me
|
homepage: https://fmoran.me
|
||||||
poster: /images/links/fmoran.me.jpg
|
poster: /images/links/fmoran.me.jpg
|
||||||
@ -55,22 +52,18 @@
|
|||||||
description: 船长の部落格,记录有趣的事,分享技术经验。
|
description: 船长の部落格,记录有趣的事,分享技术经验。
|
||||||
homepage: https://captainofphb.me
|
homepage: https://captainofphb.me
|
||||||
poster: /images/links/captainofphb.me.jpg
|
poster: /images/links/captainofphb.me.jpg
|
||||||
favicon: https://captainofphb.me/favicon.svg
|
|
||||||
- website: 小萌博客
|
- website: 小萌博客
|
||||||
description: 互联网分享笔记
|
description: 互联网分享笔记
|
||||||
homepage: https://blog.luoli.net
|
homepage: https://blog.luoli.net
|
||||||
poster: /images/links/luoli.net.jpg
|
poster: /images/links/luoli.net.jpg
|
||||||
favicon: https://blog.luoli.net/usr/themes/MUltraMoe/images/favicon.png
|
|
||||||
- website: LongLuo's Life Notes
|
- website: LongLuo's Life Notes
|
||||||
description: 每一天都是奇迹
|
description: 每一天都是奇迹
|
||||||
homepage: https://www.longluo.me
|
homepage: https://www.longluo.me
|
||||||
poster: /images/links/longluo.me.jpg
|
poster: /images/links/longluo.me.jpg
|
||||||
favicon: https://www.longluo.me/assets/logo/favicon-32x32.png
|
|
||||||
- website: 佛爷小窝
|
- website: 佛爷小窝
|
||||||
description: 一个基于内容分享,创作与灵感结合的折腾笔记博客。
|
description: 一个基于内容分享,创作与灵感结合的折腾笔记博客。
|
||||||
homepage: https://www.lafoyer.com
|
homepage: https://www.lafoyer.com
|
||||||
poster: /images/links/lafoyer.com.jpg
|
poster: /images/links/lafoyer.com.jpg
|
||||||
favicon: https://www.boxmoe.com/file/100logo.png
|
|
||||||
- website: 白熊阿丸的小屋
|
- website: 白熊阿丸的小屋
|
||||||
description: 在这里可以看到一个真实的我,我会在这里书写我的一切
|
description: 在这里可以看到一个真实的我,我会在这里书写我的一切
|
||||||
homepage: https://bxaw.name
|
homepage: https://bxaw.name
|
||||||
|
@ -7,6 +7,9 @@ tags:
|
|||||||
- 博客
|
- 博客
|
||||||
category: 编程
|
category: 编程
|
||||||
summary: 如你所见,当你访问这篇文章的时候,我已经把写了 13 年的博客程序从 WordPress 迁移到了自己用 Next.js 写的程序,这可能是我第 N 次尝试使用其他的方式写博客,但我想绝对不会是最后一次。
|
summary: 如你所见,当你访问这篇文章的时候,我已经把写了 13 年的博客程序从 WordPress 迁移到了自己用 Next.js 写的程序,这可能是我第 N 次尝试使用其他的方式写博客,但我想绝对不会是最后一次。
|
||||||
|
toc:
|
||||||
|
minHeadingLevel: 2
|
||||||
|
maxHeadingLevel: 3
|
||||||
cover: /images/2024/04/2024040719182310.jpg
|
cover: /images/2024/04/2024040719182310.jpg
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ tags:
|
|||||||
- 期房
|
- 期房
|
||||||
category: 杂谈
|
category: 杂谈
|
||||||
summary: 这篇文章试图从金融视角来期房销售的问题,也希望能回答相关投资理财遇到资损的问题。
|
summary: 这篇文章试图从金融视角来期房销售的问题,也希望能回答相关投资理财遇到资损的问题。
|
||||||
|
toc: true
|
||||||
cover: /images/2024/06/2024063015135000.jpg
|
cover: /images/2024/06/2024063015135000.jpg
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ tags:
|
|||||||
- 学习
|
- 学习
|
||||||
category: 杂谈
|
category: 杂谈
|
||||||
summary: 于我们成年人而言,虽已不能像儿童一样掌握一门外语,但是我们可通过“使用”的方式来习得英语。
|
summary: 于我们成年人而言,虽已不能像儿童一样掌握一门外语,但是我们可通过“使用”的方式来习得英语。
|
||||||
|
toc: true
|
||||||
cover: /images/2024/10/2024101621070600.jpg
|
cover: /images/2024/10/2024101621070600.jpg
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -36,10 +36,7 @@ export const pages: Page[] = pagesCollection
|
|||||||
.map((page) => ({
|
.map((page) => ({
|
||||||
slug: page.slug,
|
slug: page.slug,
|
||||||
permalink: `/${page.slug}`,
|
permalink: `/${page.slug}`,
|
||||||
render: async () => {
|
render: page.render,
|
||||||
const entry = await getEntry('pages', page.slug);
|
|
||||||
return entry.render();
|
|
||||||
},
|
|
||||||
...page.data,
|
...page.data,
|
||||||
}));
|
}));
|
||||||
export const posts: Post[] = postsCollection
|
export const posts: Post[] = postsCollection
|
||||||
@ -47,10 +44,7 @@ export const posts: Post[] = postsCollection
|
|||||||
.map((post) => ({
|
.map((post) => ({
|
||||||
slug: post.slug,
|
slug: post.slug,
|
||||||
permalink: `/posts/${post.slug}`,
|
permalink: `/posts/${post.slug}`,
|
||||||
render: async () => {
|
render: post.render,
|
||||||
const entry = await getEntry('posts', post.slug);
|
|
||||||
return entry.render();
|
|
||||||
},
|
|
||||||
raw: async () => {
|
raw: async () => {
|
||||||
const entry = await getEntry('posts', post.slug);
|
const entry = await getEntry('posts', post.slug);
|
||||||
return entry.body;
|
return entry.body;
|
||||||
|
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 LikeButton from '@/components/like/LikeButton.astro';
|
||||||
import PageMeta from '@/components/meta/PageMeta.astro';
|
import PageMeta from '@/components/meta/PageMeta.astro';
|
||||||
import Friends from '@/components/page/friend/Friends.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 MusicPlayer from '@/components/player/MusicPlayer.astro';
|
||||||
import type { Page } from '@/helpers/schema';
|
import type { Page } from '@/helpers/schema';
|
||||||
import { urlJoin } from '@/helpers/tools';
|
import { urlJoin } from '@/helpers/tools';
|
||||||
@ -15,7 +16,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { page } = Astro.props;
|
const { page } = Astro.props;
|
||||||
const { Content } = await page.render();
|
const { Content, headings } = await page.render();
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={page.title}>
|
<BaseLayout title={page.title}>
|
||||||
@ -25,6 +26,7 @@ const { Content } = await page.render();
|
|||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="post">
|
<div class="post">
|
||||||
<h1 class="post-title mb-3 mb-xl-4">{page.title}</h1>
|
<h1 class="post-title mb-3 mb-xl-4">{page.title}</h1>
|
||||||
|
<TableOfContents {headings} toc={page.toc} />
|
||||||
<div class="post-content">
|
<div class="post-content">
|
||||||
<div class="nc-light-gallery">
|
<div class="nc-light-gallery">
|
||||||
<Content components={{ MusicPlayer: MusicPlayer, Image: Image }} />
|
<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 LikeButton from '@/components/like/LikeButton.astro';
|
||||||
import LikeShare from '@/components/like/LikeShare.astro';
|
import LikeShare from '@/components/like/LikeShare.astro';
|
||||||
import PostMeta from '@/components/meta/PostMeta.astro';
|
import PostMeta from '@/components/meta/PostMeta.astro';
|
||||||
|
import TableOfContents from '@/components/page/toc/TableOfContents.astro';
|
||||||
import MusicPlayer from '@/components/player/MusicPlayer.astro';
|
import MusicPlayer from '@/components/player/MusicPlayer.astro';
|
||||||
import Sidebar from '@/components/sidebar/Sidebar.astro';
|
import Sidebar from '@/components/sidebar/Sidebar.astro';
|
||||||
import { formatShowDate } from '@/helpers/formatter';
|
import { formatShowDate } from '@/helpers/formatter';
|
||||||
@ -17,7 +18,7 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { post } = Astro.props;
|
const { post } = Astro.props;
|
||||||
const { Content } = await post.render();
|
const { Content, headings } = await post.render();
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title={post.title}>
|
<BaseLayout title={post.title}>
|
||||||
@ -53,6 +54,7 @@ const { Content } = await post.render();
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<TableOfContents {headings} toc={post.toc} />
|
||||||
<div class="post-content">
|
<div class="post-content">
|
||||||
<div class="nc-light-gallery">
|
<div class="nc-light-gallery">
|
||||||
<Content components={{ MusicPlayer: MusicPlayer, Image: Image }} />
|
<Content components={{ MusicPlayer: MusicPlayer, Image: Image }} />
|
||||||
|
Loading…
Reference in New Issue
Block a user