mirror of
https://github.com/bspeice/speice.io
synced 2024-12-22 08:38:09 -05:00
Handle blog series
This commit is contained in:
parent
7580df1dd4
commit
7426890685
@ -1,11 +1,11 @@
|
||||
---
|
||||
slug: 2015/11/welcome
|
||||
title: Welcome, and an algorithm
|
||||
date: 2015-11-19 12:00:00
|
||||
last_update:
|
||||
date: 2015-12-05 12:00:00
|
||||
slug: 2015/11/welcome
|
||||
authors: [bspeice]
|
||||
tags: [trading]
|
||||
tags: []
|
||||
---
|
||||
|
||||
Hello! Glad to meet you. I'm currently a student at Columbia University studying Financial Engineering, and want to give an overview of the projects I'm working on!
|
||||
@ -17,7 +17,7 @@ To start things off, Columbia has been hosting a trading competition that myself
|
||||
The competition is scored in 3 areas:
|
||||
|
||||
- Total return
|
||||
- [Sharpe ratio](1)
|
||||
- [Sharpe ratio](https://en.wikipedia.org/wiki/Sharpe_ratio)
|
||||
- Maximum drawdown
|
||||
|
||||
Our algorithm uses a basic momentum strategy: in the given list of potential portfolios, pick the stocks that have been performing well in the past 30 days. Then, optimize for return subject to the drawdown being below a specific level. We didn't include the Sharpe ratio as a constraint, mostly because we were a bit late entering the competition.
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: 2018/01/captains-cookbook-part-1
|
||||
title: Captain's cookbook - part 1
|
||||
title: "Captain's Cookbook: Project setup"
|
||||
date: 2018-01-16 12:00:00
|
||||
authors: [bspeice]
|
||||
tags: []
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: 2018/01/captains-cookbook-part-2
|
||||
title: Captain's cookbook - part 2
|
||||
title: "Captain's Cookbook: Practical usage"
|
||||
date: 2018-01-16 13:00:00
|
||||
authors: [bspeice]
|
||||
tags: []
|
||||
|
@ -1,6 +1,6 @@
|
||||
---
|
||||
slug: 2018/06/dateutil-parser-to-rust
|
||||
title: "What I Learned: Porting Dateutil Parser to Rust"
|
||||
title: "What I learned porting dateutil to Rust"
|
||||
date: 2018-06-25 12:00:00
|
||||
authors: [bspeice]
|
||||
tags: []
|
||||
|
218
blog/2018-12-15-allocation-safety/_article.md
Normal file
218
blog/2018-12-15-allocation-safety/_article.md
Normal file
@ -0,0 +1,218 @@
|
||||
---
|
||||
layout: post
|
||||
title: "QADAPT - debug_assert! for your memory usage"
|
||||
description: "...and why you want an allocator that goes 💥."
|
||||
category:
|
||||
tags: []
|
||||
---
|
||||
|
||||
I think it's part of the human condition to ignore perfectly good advice when it comes our way. A
|
||||
bit over a month ago, I was dispensing sage wisdom for the ages:
|
||||
|
||||
> I had a really great idea: build a custom allocator that allows you to track your own allocations.
|
||||
> I gave it a shot, but learned very quickly: **never write your own allocator.**
|
||||
>
|
||||
> -- [me](/2018/10/case-study-optimization.html)
|
||||
|
||||
I proceeded to ignore it, because we never really learn from our mistakes.
|
||||
|
||||
There's another part of the human condition that derives joy from seeing things explode.
|
||||
|
||||
<iframe src="https://giphy.com/embed/YA6dmVW0gfIw8" width="480" height="336" frameBorder="0"></iframe>
|
||||
|
||||
And _that's_ the part I'm going to focus on.
|
||||
|
||||
# Why an Allocator?
|
||||
|
||||
So why, after complaining about allocators, would I still want to write one? There are three reasons
|
||||
for that:
|
||||
|
||||
1. Allocation/dropping is slow
|
||||
2. It's difficult to know exactly when Rust will allocate or drop, especially when using code that
|
||||
you did not write
|
||||
3. I want automated tools to verify behavior, instead of inspecting by hand
|
||||
|
||||
When I say "slow," it's important to define the terms. If you're writing web applications, you'll
|
||||
spend orders of magnitude more time waiting for the database than you will the allocator. However,
|
||||
there's still plenty of code where micro- or nano-seconds matter; think
|
||||
[finance](https://www.youtube.com/watch?v=NH1Tta7purM),
|
||||
[real-time audio](https://www.reddit.com/r/rust/comments/9hg7yj/synthesizer_progress_update/e6c291f),
|
||||
[self-driving cars](https://polysync.io/blog/session-types-for-hearty-codecs/), and
|
||||
[networking](https://carllerche.github.io/bytes/bytes/index.html). In these situations it's simply
|
||||
unacceptable for you to spend time doing things that are not your program, and waiting on the
|
||||
allocator is not cool.
|
||||
|
||||
As I continue to learn Rust, it's difficult for me to predict where exactly allocations will happen.
|
||||
So, I propose we play a quick trivia game: **Does this code invoke the allocator?**
|
||||
|
||||
## Example 1
|
||||
|
||||
```rust
|
||||
fn my_function() {
|
||||
let v: Vec<u8> = Vec::new();
|
||||
}
|
||||
```
|
||||
|
||||
**No**: Rust [knows how big](https://doc.rust-lang.org/std/mem/fn.size_of.html) the `Vec` type is,
|
||||
and reserves a fixed amount of memory on the stack for the `v` vector. However, if we wanted to
|
||||
reserve extra space (using `Vec::with_capacity`) the allocator would get invoked.
|
||||
|
||||
## Example 2
|
||||
|
||||
```rust
|
||||
fn my_function() {
|
||||
let v: Box<Vec<u8>> = Box::new(Vec::new());
|
||||
}
|
||||
```
|
||||
|
||||
**Yes**: Because Boxes allow us to work with things that are of unknown size, it has to allocate on
|
||||
the heap. While the `Box` is unnecessary in this snippet (release builds will optimize out the
|
||||
allocation), reserving heap space more generally is needed to pass a dynamically sized type to
|
||||
another function.
|
||||
|
||||
## Example 3
|
||||
|
||||
```rust
|
||||
fn my_function(v: Vec<u8>) {
|
||||
v.push(5);
|
||||
}
|
||||
```
|
||||
|
||||
**Maybe**: Depending on whether the Vector we were given has space available, we may or may not
|
||||
allocate. Especially when dealing with code that you did not author, it's difficult to verify that
|
||||
things behave as you expect them to.
|
||||
|
||||
# Blowing Things Up
|
||||
|
||||
So, how exactly does QADAPT solve these problems? **Whenever an allocation or drop occurs in code
|
||||
marked allocation-safe, QADAPT triggers a thread panic.** We don't want to let the program continue
|
||||
as if nothing strange happened, _we want things to explode_.
|
||||
|
||||
However, you don't want code to panic in production because of circumstances you didn't predict.
|
||||
Just like [`debug_assert!`](https://doc.rust-lang.org/std/macro.debug_assert.html), **QADAPT will
|
||||
strip out its own code when building in release mode to guarantee no panics and no performance
|
||||
impact.**
|
||||
|
||||
Finally, there are three ways to have QADAPT check that your code will not invoke the allocator:
|
||||
|
||||
## Using a procedural macro
|
||||
|
||||
The easiest method, watch an entire function for allocator invocation:
|
||||
|
||||
```rust
|
||||
use qadapt::no_alloc;
|
||||
use qadapt::QADAPT;
|
||||
|
||||
#[global_allocator]
|
||||
static Q: QADAPT = QADAPT;
|
||||
|
||||
#[no_alloc]
|
||||
fn push_vec(v: &mut Vec<u8>) {
|
||||
// This triggers a panic if v.len() == v.capacity()
|
||||
v.push(5);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let v = Vec::with_capacity(1);
|
||||
|
||||
// This will *not* trigger a panic
|
||||
push_vec(&v);
|
||||
|
||||
// This *will* trigger a panic
|
||||
push_vec(&v);
|
||||
}
|
||||
```
|
||||
|
||||
## Using a regular macro
|
||||
|
||||
For times when you need more precision:
|
||||
|
||||
```rust
|
||||
use qadapt::assert_no_alloc;
|
||||
use qadapt::QADAPT;
|
||||
|
||||
#[global_allocator]
|
||||
static Q: QADAPT = QADAPT;
|
||||
|
||||
fn main() {
|
||||
let v = Vec::with_capacity(1);
|
||||
|
||||
// No allocations here, we already have space reserved
|
||||
assert_no_alloc!(v.push(5));
|
||||
|
||||
// Even though we remove an item, it doesn't trigger a drop
|
||||
// because it's a scalar. If it were a `Box<_>` type,
|
||||
// a drop would trigger.
|
||||
assert_no_alloc!({
|
||||
v.pop().unwrap();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Using function calls
|
||||
|
||||
Both the most precise and most tedious:
|
||||
|
||||
```rust
|
||||
use qadapt::enter_protected;
|
||||
use qadapt::exit_protected;
|
||||
use qadapt::QADAPT;
|
||||
|
||||
#[global_allocator]
|
||||
static Q: QADAPT = QADAPT;
|
||||
|
||||
fn main() {
|
||||
// This triggers an allocation (on non-release builds)
|
||||
let v = Vec::with_capacity(1);
|
||||
|
||||
enter_protected();
|
||||
// This does not trigger an allocation because we've reserved size
|
||||
v.push(0);
|
||||
exit_protected();
|
||||
|
||||
// This triggers an allocation because we ran out of size,
|
||||
// but doesn't panic because we're no longer protected.
|
||||
v.push(1);
|
||||
}
|
||||
```
|
||||
|
||||
## Caveats
|
||||
|
||||
It's important to point out that QADAPT code is synchronous, so please be careful when mixing in
|
||||
asynchronous functions:
|
||||
|
||||
```rust
|
||||
use futures::future::Future;
|
||||
use futures::future::ok;
|
||||
|
||||
#[no_alloc]
|
||||
fn async_capacity() -> impl Future<Item=Vec<u8>, Error=()> {
|
||||
ok(12).and_then(|e| Ok(Vec::with_capacity(e)))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// This doesn't trigger a panic because the `and_then` closure
|
||||
// wasn't run during the function call.
|
||||
async_capacity();
|
||||
|
||||
// Still no panic
|
||||
assert_no_alloc!(async_capacity());
|
||||
|
||||
// This will panic because the allocation happens during `unwrap`
|
||||
// in the `assert_no_alloc!` macro
|
||||
assert_no_alloc!(async_capacity().poll().unwrap());
|
||||
}
|
||||
```
|
||||
|
||||
# Conclusion
|
||||
|
||||
While there's a lot more to writing high-performance code than managing your usage of the allocator,
|
||||
it's critical that you do use the allocator correctly. QADAPT will verify that your code is doing
|
||||
what you expect. It's usable even on stable Rust from version 1.31 onward, which isn't the case for
|
||||
most allocators. Version 1.0 was released today, and you can check it out over at
|
||||
[crates.io](https://crates.io/crates/qadapt) or on [github](https://github.com/bspeice/qadapt).
|
||||
|
||||
I'm hoping to write more about high-performance Rust in the future, and I expect that QADAPT will
|
||||
help guide that. If there are topics you're interested in, let me know in the comments below!
|
||||
|
||||
[qadapt]: https://crates.io/crates/qadapt
|
222
blog/2018-12-15-allocation-safety/index.mdx
Normal file
222
blog/2018-12-15-allocation-safety/index.mdx
Normal file
@ -0,0 +1,222 @@
|
||||
---
|
||||
slug: 2018/12/allocation-safety
|
||||
title: "QADAPT - debug_assert! for allocations"
|
||||
date: 2018-12-15 12:00:00
|
||||
authors: [bspeice]
|
||||
tags: []
|
||||
---
|
||||
|
||||
I think it's part of the human condition to ignore perfectly good advice when it comes our way. A
|
||||
bit over a month ago, I was dispensing sage wisdom for the ages:
|
||||
|
||||
> I had a really great idea: build a custom allocator that allows you to track your own allocations.
|
||||
> I gave it a shot, but learned very quickly: **never write your own allocator.**
|
||||
>
|
||||
> -- [me](../2018-10-08-case-study-optimization)
|
||||
|
||||
I proceeded to ignore it, because we never really learn from our mistakes.
|
||||
|
||||
<!-- truncate -->
|
||||
|
||||
There's another part of the human condition that derives joy from seeing things explode.
|
||||
|
||||
<center>
|
||||
![Explosions](./watch-the-world-burn.webp)
|
||||
</center>
|
||||
|
||||
And _that's_ the part I'm going to focus on.
|
||||
|
||||
## Why an Allocator?
|
||||
|
||||
So why, after complaining about allocators, would I still want to write one? There are three reasons
|
||||
for that:
|
||||
|
||||
1. Allocation/dropping is slow
|
||||
2. It's difficult to know exactly when Rust will allocate or drop, especially when using code that
|
||||
you did not write
|
||||
3. I want automated tools to verify behavior, instead of inspecting by hand
|
||||
|
||||
When I say "slow," it's important to define the terms. If you're writing web applications, you'll
|
||||
spend orders of magnitude more time waiting for the database than you will the allocator. However,
|
||||
there's still plenty of code where micro- or nano-seconds matter; think
|
||||
[finance](https://www.youtube.com/watch?v=NH1Tta7purM),
|
||||
[real-time audio](https://www.reddit.com/r/rust/comments/9hg7yj/synthesizer_progress_update/e6c291f),
|
||||
[self-driving cars](https://polysync.io/blog/session-types-for-hearty-codecs/), and
|
||||
[networking](https://carllerche.github.io/bytes/bytes/index.html). In these situations it's simply
|
||||
unacceptable for you to spend time doing things that are not your program, and waiting on the
|
||||
allocator is not cool.
|
||||
|
||||
As I continue to learn Rust, it's difficult for me to predict where exactly allocations will happen.
|
||||
So, I propose we play a quick trivia game: **Does this code invoke the allocator?**
|
||||
|
||||
### Example 1
|
||||
|
||||
```rust
|
||||
fn my_function() {
|
||||
let v: Vec<u8> = Vec::new();
|
||||
}
|
||||
```
|
||||
|
||||
**No**: Rust [knows how big](https://doc.rust-lang.org/std/mem/fn.size_of.html) the `Vec` type is,
|
||||
and reserves a fixed amount of memory on the stack for the `v` vector. However, if we wanted to
|
||||
reserve extra space (using `Vec::with_capacity`) the allocator would get invoked.
|
||||
|
||||
### Example 2
|
||||
|
||||
```rust
|
||||
fn my_function() {
|
||||
let v: Box<Vec<u8>> = Box::new(Vec::new());
|
||||
}
|
||||
```
|
||||
|
||||
**Yes**: Because Boxes allow us to work with things that are of unknown size, it has to allocate on
|
||||
the heap. While the `Box` is unnecessary in this snippet (release builds will optimize out the
|
||||
allocation), reserving heap space more generally is needed to pass a dynamically sized type to
|
||||
another function.
|
||||
|
||||
### Example 3
|
||||
|
||||
```rust
|
||||
fn my_function(v: Vec<u8>) {
|
||||
v.push(5);
|
||||
}
|
||||
```
|
||||
|
||||
**Maybe**: Depending on whether the Vector we were given has space available, we may or may not
|
||||
allocate. Especially when dealing with code that you did not author, it's difficult to verify that
|
||||
things behave as you expect them to.
|
||||
|
||||
## Blowing Things Up
|
||||
|
||||
So, how exactly does QADAPT solve these problems? **Whenever an allocation or drop occurs in code
|
||||
marked allocation-safe, QADAPT triggers a thread panic.** We don't want to let the program continue
|
||||
as if nothing strange happened, _we want things to explode_.
|
||||
|
||||
However, you don't want code to panic in production because of circumstances you didn't predict.
|
||||
Just like [`debug_assert!`](https://doc.rust-lang.org/std/macro.debug_assert.html), **QADAPT will
|
||||
strip out its own code when building in release mode to guarantee no panics and no performance
|
||||
impact.**
|
||||
|
||||
Finally, there are three ways to have QADAPT check that your code will not invoke the allocator:
|
||||
|
||||
### Using a procedural macro
|
||||
|
||||
The easiest method, watch an entire function for allocator invocation:
|
||||
|
||||
```rust
|
||||
use qadapt::no_alloc;
|
||||
use qadapt::QADAPT;
|
||||
|
||||
#[global_allocator]
|
||||
static Q: QADAPT = QADAPT;
|
||||
|
||||
#[no_alloc]
|
||||
fn push_vec(v: &mut Vec<u8>) {
|
||||
// This triggers a panic if v.len() == v.capacity()
|
||||
v.push(5);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let v = Vec::with_capacity(1);
|
||||
|
||||
// This will *not* trigger a panic
|
||||
push_vec(&v);
|
||||
|
||||
// This *will* trigger a panic
|
||||
push_vec(&v);
|
||||
}
|
||||
```
|
||||
|
||||
### Using a regular macro
|
||||
|
||||
For times when you need more precision:
|
||||
|
||||
```rust
|
||||
use qadapt::assert_no_alloc;
|
||||
use qadapt::QADAPT;
|
||||
|
||||
#[global_allocator]
|
||||
static Q: QADAPT = QADAPT;
|
||||
|
||||
fn main() {
|
||||
let v = Vec::with_capacity(1);
|
||||
|
||||
// No allocations here, we already have space reserved
|
||||
assert_no_alloc!(v.push(5));
|
||||
|
||||
// Even though we remove an item, it doesn't trigger a drop
|
||||
// because it's a scalar. If it were a `Box<_>` type,
|
||||
// a drop would trigger.
|
||||
assert_no_alloc!({
|
||||
v.pop().unwrap();
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### Using function calls
|
||||
|
||||
Both the most precise and most tedious:
|
||||
|
||||
```rust
|
||||
use qadapt::enter_protected;
|
||||
use qadapt::exit_protected;
|
||||
use qadapt::QADAPT;
|
||||
|
||||
#[global_allocator]
|
||||
static Q: QADAPT = QADAPT;
|
||||
|
||||
fn main() {
|
||||
// This triggers an allocation (on non-release builds)
|
||||
let v = Vec::with_capacity(1);
|
||||
|
||||
enter_protected();
|
||||
// This does not trigger an allocation because we've reserved size
|
||||
v.push(0);
|
||||
exit_protected();
|
||||
|
||||
// This triggers an allocation because we ran out of size,
|
||||
// but doesn't panic because we're no longer protected.
|
||||
v.push(1);
|
||||
}
|
||||
```
|
||||
|
||||
### Caveats
|
||||
|
||||
It's important to point out that QADAPT code is synchronous, so please be careful when mixing in
|
||||
asynchronous functions:
|
||||
|
||||
```rust
|
||||
use futures::future::Future;
|
||||
use futures::future::ok;
|
||||
|
||||
#[no_alloc]
|
||||
fn async_capacity() -> impl Future<Item=Vec<u8>, Error=()> {
|
||||
ok(12).and_then(|e| Ok(Vec::with_capacity(e)))
|
||||
}
|
||||
|
||||
fn main() {
|
||||
// This doesn't trigger a panic because the `and_then` closure
|
||||
// wasn't run during the function call.
|
||||
async_capacity();
|
||||
|
||||
// Still no panic
|
||||
assert_no_alloc!(async_capacity());
|
||||
|
||||
// This will panic because the allocation happens during `unwrap`
|
||||
// in the `assert_no_alloc!` macro
|
||||
assert_no_alloc!(async_capacity().poll().unwrap());
|
||||
}
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
While there's a lot more to writing high-performance code than managing your usage of the allocator,
|
||||
it's critical that you do use the allocator correctly. QADAPT will verify that your code is doing
|
||||
what you expect. It's usable even on stable Rust from version 1.31 onward, which isn't the case for
|
||||
most allocators. Version 1.0 was released today, and you can check it out over at
|
||||
[crates.io](https://crates.io/crates/qadapt) or on [github](https://github.com/bspeice/qadapt).
|
||||
|
||||
I'm hoping to write more about high-performance Rust in the future, and I expect that QADAPT will
|
||||
help guide that. If there are topics you're interested in, let me know in the comments below!
|
||||
|
||||
[qadapt]: https://crates.io/crates/qadapt
|
BIN
blog/2018-12-15-allocation-safety/watch-the-world-burn.webp
Normal file
BIN
blog/2018-12-15-allocation-safety/watch-the-world-burn.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 MiB |
@ -1,6 +1,8 @@
|
||||
:root {
|
||||
--ifm-container-width: 1280px;
|
||||
--ifm-container-width-xl: 1440px;
|
||||
--ifm-footer-padding-vertical: .5rem;
|
||||
--ifm-spacing-horizontal: .8rem;
|
||||
}
|
||||
|
||||
.header-github-link:hover {
|
||||
|
@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import clsx from 'clsx';
|
||||
import Layout from '@theme/Layout';
|
||||
import BlogSidebar from '@theme/BlogSidebar';
|
||||
|
||||
import type {Props} from '@theme/BlogLayout';
|
||||
|
||||
export default function BlogLayout(props: Props): JSX.Element {
|
||||
const {sidebar, toc, children, ...layoutProps} = props;
|
||||
const hasSidebar = sidebar && sidebar.items.length > 0;
|
||||
|
||||
return (
|
||||
<Layout {...layoutProps}>
|
||||
<div className="container margin-vert--lg">
|
||||
<div className="row">
|
||||
<BlogSidebar sidebar={sidebar} />
|
||||
<main
|
||||
className={clsx('col', {
|
||||
'col--8': hasSidebar,
|
||||
'col--10 col--offset-1': !hasSidebar,
|
||||
})}>
|
||||
{children}
|
||||
</main>
|
||||
{toc && <div className="col col--2">{toc}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
/**
|
||||
* Docusaurus typically puts the newer post on the left button,
|
||||
* and older posts on the right. This file exists to swap them.
|
||||
*/
|
||||
import React from 'react';
|
||||
import Translate, {translate} from '@docusaurus/Translate';
|
||||
import PaginatorNavLink from '@theme/PaginatorNavLink';
|
||||
|
120
src/theme/BlogSidebar/Content/index.tsx
Normal file
120
src/theme/BlogSidebar/Content/index.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Use post titles to infer blog post series
|
||||
*/
|
||||
import React, { memo, type ReactNode } from 'react';
|
||||
import Heading, { HeadingType } from '@theme/Heading';
|
||||
import type { Props } from '@theme/BlogSidebar/Content';
|
||||
import { BlogSidebarItem } from '@docusaurus/plugin-content-blog';
|
||||
|
||||
|
||||
function BlogSidebarGroup({ title, headingType, children }: { title: string, headingType: HeadingType, children: ReactNode }) {
|
||||
return (
|
||||
<div role="group">
|
||||
<Heading as={headingType}>
|
||||
{title}
|
||||
</Heading>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function groupBySeries(items: BlogSidebarItem[], ListComponent: Props["ListComponent"]) {
|
||||
var returnItems = [];
|
||||
var seriesItems: BlogSidebarItem[] = [];
|
||||
|
||||
function flushSeries() {
|
||||
if (seriesItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const seriesTitle = seriesItems[0].title.split(":")[0];
|
||||
|
||||
// Strip the series name from the titles
|
||||
seriesItems = seriesItems.map(item => {
|
||||
return {
|
||||
...item,
|
||||
title: item.title.split(":")[1].trim(),
|
||||
}
|
||||
});
|
||||
|
||||
// Reverse the display ordering - normally blog items are shown in descending time order,
|
||||
// but for a series, we want to show ascending order
|
||||
seriesItems = seriesItems.reverse();
|
||||
|
||||
returnItems.push(<>
|
||||
<BlogSidebarGroup title={seriesTitle} headingType='h4'>
|
||||
<ul>
|
||||
<ListComponent items={seriesItems} />
|
||||
</ul>
|
||||
</BlogSidebarGroup>
|
||||
</>);
|
||||
|
||||
seriesItems = [];
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
// If this item is part of a series, begin accumulating
|
||||
if (item.title.includes(":")) {
|
||||
seriesItems.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
flushSeries();
|
||||
|
||||
returnItems.push(<ListComponent items={[item]} />);
|
||||
}
|
||||
|
||||
flushSeries();
|
||||
return returnItems;
|
||||
}
|
||||
|
||||
function groupByYear(items: BlogSidebarItem[], ListComponent: Props["ListComponent"]) {
|
||||
var returnItems = [];
|
||||
var yearItems: BlogSidebarItem[] = [];
|
||||
|
||||
function flushSeries() {
|
||||
if (yearItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const yearTitle = new Date(yearItems[0].date).getFullYear();
|
||||
const yearItemsGrouped = groupBySeries(yearItems, ListComponent);
|
||||
|
||||
returnItems.push(<>
|
||||
<BlogSidebarGroup title={String(yearTitle)} headingType='h3'>
|
||||
{yearItemsGrouped}
|
||||
</BlogSidebarGroup>
|
||||
</>);
|
||||
|
||||
yearItems = [];
|
||||
}
|
||||
|
||||
for (const item of items) {
|
||||
if (yearItems.length === 0) {
|
||||
yearItems.push(item);
|
||||
continue;
|
||||
}
|
||||
|
||||
const itemYear = new Date(item.date).getFullYear();
|
||||
const currentYear = new Date(yearItems[0].date).getFullYear();
|
||||
|
||||
if (itemYear !== currentYear) {
|
||||
flushSeries();
|
||||
}
|
||||
|
||||
yearItems.push(item);
|
||||
}
|
||||
|
||||
flushSeries();
|
||||
return returnItems;
|
||||
}
|
||||
|
||||
function BlogSidebarContent({
|
||||
items,
|
||||
yearGroupHeadingClassName,
|
||||
ListComponent,
|
||||
}: Props): ReactNode {
|
||||
return groupByYear(items, ListComponent);
|
||||
}
|
||||
|
||||
export default memo(BlogSidebarContent);
|
@ -1,50 +0,0 @@
|
||||
import React, {memo} from 'react';
|
||||
import clsx from 'clsx';
|
||||
import {translate} from '@docusaurus/Translate';
|
||||
import {
|
||||
useVisibleBlogSidebarItems,
|
||||
BlogSidebarItemList,
|
||||
} from '@docusaurus/plugin-content-blog/client';
|
||||
import BlogSidebarContent from '@theme/BlogSidebar/Content';
|
||||
import type {Props as BlogSidebarContentProps} from '@theme/BlogSidebar/Content';
|
||||
import type {Props} from '@theme/BlogSidebar/Desktop';
|
||||
|
||||
import styles from './styles.module.css';
|
||||
|
||||
const ListComponent: BlogSidebarContentProps['ListComponent'] = ({items}) => {
|
||||
return (
|
||||
<BlogSidebarItemList
|
||||
items={items}
|
||||
ulClassName={clsx(styles.sidebarItemList, 'clean-list')}
|
||||
liClassName={styles.sidebarItem}
|
||||
linkClassName={styles.sidebarItemLink}
|
||||
linkActiveClassName={styles.sidebarItemLinkActive}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function BlogSidebarDesktop({sidebar}: Props) {
|
||||
const items = useVisibleBlogSidebarItems(sidebar.items);
|
||||
return (
|
||||
<aside className="col col--2">
|
||||
<nav
|
||||
className={clsx(styles.sidebar, 'thin-scrollbar')}
|
||||
aria-label={translate({
|
||||
id: 'theme.blog.sidebar.navAriaLabel',
|
||||
message: 'Blog recent posts navigation',
|
||||
description: 'The ARIA label for recent posts in the blog sidebar',
|
||||
})}>
|
||||
<div className={clsx(styles.sidebarItemTitle, 'margin-bottom--md')}>
|
||||
{sidebar.title}
|
||||
</div>
|
||||
<BlogSidebarContent
|
||||
items={items}
|
||||
ListComponent={ListComponent}
|
||||
yearGroupHeadingClassName={styles.yearGroupHeading}
|
||||
/>
|
||||
</nav>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(BlogSidebarDesktop);
|
@ -1,45 +0,0 @@
|
||||
.sidebar {
|
||||
max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem));
|
||||
overflow-y: auto;
|
||||
position: sticky;
|
||||
top: calc(var(--ifm-navbar-height) + 2rem);
|
||||
padding-right: 1px;
|
||||
border-right: 1px solid var(--ifm-toc-border-color);
|
||||
}
|
||||
|
||||
.sidebarItemTitle {
|
||||
font-size: var(--ifm-h3-font-size);
|
||||
font-weight: var(--ifm-font-weight-bold);
|
||||
}
|
||||
|
||||
.sidebarItemList {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.sidebarItem {
|
||||
margin-top: 0.7rem;
|
||||
}
|
||||
|
||||
.sidebarItemLink {
|
||||
color: var(--ifm-font-color-base);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sidebarItemLink:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sidebarItemLinkActive {
|
||||
color: var(--ifm-color-primary) !important;
|
||||
}
|
||||
|
||||
@media (max-width: 996px) {
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.yearGroupHeading {
|
||||
margin-top: 1.6rem;
|
||||
margin-bottom: 0.4rem;
|
||||
}
|
Loading…
Reference in New Issue
Block a user