Skip to content

Commit

Permalink
docs: add blog setup (#6170)
Browse files Browse the repository at this point in the history
  • Loading branch information
doodlewind committed Jan 31, 2024
1 parent 1554e65 commit 0e8fa4f
Show file tree
Hide file tree
Showing 15 changed files with 245 additions and 14 deletions.
2 changes: 1 addition & 1 deletion packages/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default defineConfig({
{ text: 'API', link: '/api/' },
],
},
// { text: 'Blog', link: '/blog/' },
{ text: 'Blog', link: '/blog/', activeMatch: '/blog/*' },
{
text: 'Releases',
link: 'https://github.com/toeverything/blocksuite/releases',
Expand Down
77 changes: 77 additions & 0 deletions packages/docs/.vitepress/theme/components/blog-list-layout.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<h1>BlockSuite Blog</h1>
<div class="blog-posts-container">
<div class="blog-post" v-for="post in posts">
<a :href="post.url">
<h2 class="blog-post-title">{{ post.title }}</h2>
</a>
<div class="blog-post-excerpt">
{{ post.excerpt }}
<a class="blog-post-read-more" :href="post.url">Read more →</a>
</div>
<div class="blog-post-date">{{ post.date.formatted }}</div>
</div>
</div>
</template>

<script setup>
import { usePosts } from '../composables/use-posts';
const { posts } = usePosts();
</script>

<style scoped>
h1 {
margin: auto;
margin-top: 30px;
margin-bottom: 50px;
font-size: 50px;
font-weight: bolder;
line-height: 50px;
text-align: center;
}
@media (max-width: 768px) {
h1 {
font-size: 40px;
line-height: 40px;
}
}
.blog-posts-container {
max-width: 800px;
margin: 0 auto;
}
.blog-post {
margin-bottom: 40px;
padding-bottom: 20px;
border-bottom: 1px solid #eaecef;
}
.blog-post-title {
margin: 0;
font-size: 24px;
font-weight: bold;
color: var(--vp-c-text-1);
}
.blog-post-date {
font-size: 14px;
color: var(--vp-c-text-3);
}
.blog-post-excerpt {
margin-top: 10px;
margin-bottom: 5px;
line-height: 1.6;
}
.dark .blog-post {
border-bottom: 1px solid #343a40;
}
.blog-post-read-more:hover {
text-decoration: underline;
}
</style>
35 changes: 35 additions & 0 deletions packages/docs/.vitepress/theme/components/blog-post-meta.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<template>
<div class="blog-post-meta">
<span class="post-date">{{ post.date.formatted }}</span> by
<span v-html="formattedAuthors"></span>
</div>
</template>

<script setup>
import { computed } from 'vue';
import { usePosts } from '../composables/use-posts';
const { post } = usePosts();
const formattedAuthors = computed(() => {
return post.value.authors
.map(
author =>
`<a class="author" href="${author.link}" target="_blank">${author.name}</a>`
)
.join(', ');
});
</script>

<style>
.blog-post-meta {
margin-top: 8px;
font-size: 14px;
}
.author {
color: var(--vp-c-text-1) !important;
}
.post-date {
color: var(--vp-c-text-2);
}
</style>
File renamed without changes.
44 changes: 44 additions & 0 deletions packages/docs/.vitepress/theme/composables/posts.data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { format, formatDistance } from 'date-fns';
import { createContentLoader } from 'vitepress';

interface Post {
title: string;
authors: { name: string; link: string }[];
url: string;
date: {
raw: string;
time: number;
formatted: string;
since: string;
};
}

function formatDate(raw: string) {
const date = new Date(raw);
date.setUTCHours(8);
return {
raw: date.toISOString().split('T')[0],
time: +date,
formatted: format(date, 'yyyy/MM/dd'),
since: formatDistance(date, new Date(), { addSuffix: true }),
};
}

const data = [] as Post[];
export { data };

export default createContentLoader('blog/*.md', {
includeSrc: true,
transform(raw) {
return raw
.filter(item => item.url !== '/blog/')
.map(({ url, frontmatter }) => ({
title: frontmatter.title,
authors: frontmatter.authors ?? [],
excerpt: frontmatter.excerpt ?? '',
url,
date: formatDate(frontmatter.date),
}))
.sort((a, b) => b.date.time - a.date.time);
},
});
18 changes: 18 additions & 0 deletions packages/docs/.vitepress/theme/composables/use-posts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { computed } from 'vue';
import { useRoute } from 'vitepress';
import { data as posts } from './posts.data';

export function usePosts() {
const route = useRoute();
const path = route.path;

function findCurrentIndex() {
const result = posts.findIndex(p => p.url === route.path);
if (result === -1) console.error(`blog post missing: ${route.path}`);
return result;
}

const post = computed(() => posts[findCurrentIndex()]);

return { posts, post, path };
}
10 changes: 7 additions & 3 deletions packages/docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// https://vitepress.dev/guide/custom-theme
import { h } from 'vue';
import Theme from 'vitepress/theme';
import Logo from './logo.vue';
import Playground from './playground.vue';
import CodeSandbox from './code-sandbox.vue';
import Logo from './components/logo.vue';
import Playground from './components/playground.vue';
import BlogListLayout from './components/blog-list-layout.vue';
import BlogPostMeta from './components/blog-post-meta.vue';
import CodeSandbox from './components/code-sandbox.vue';
import 'vitepress-plugin-sandpack/dist/style.css';
import './style.css';

Expand All @@ -17,6 +19,8 @@ export default {
});
},
enhanceApp({ app, router, siteData }) {
app.component('BlogListLayout', BlogListLayout);
app.component('BlogPostMeta', BlogPostMeta);
app.component('CodeSandbox', CodeSandbox);
},
};
44 changes: 42 additions & 2 deletions packages/docs/blog/crdt-native-data-flow.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
# CRDT-Native Data Flow
---
title: CRDT-Native Data Flow in BlockSuite
date: 2023-04-15
authors:
- name: Yifeng Wang
link: 'https://twitter.com/ewind1994'
- name: Saul-Mirone
link: 'https://github.com/Saul-Mirone'
excerpt: To make editors intuitive and collaboration-ready, BlockSuite ensure that regardless of whether you are collaborating with others or not, the application code should be unaware of it. This article introduce how this is designed.
---

# CRDT-Native Data Flow in BlockSuite

<BlogPostMeta />

To make the editor logic based on BlockSuite intuitive and collaboration-ready, there is one major goal in BlockSuite: **Regardless of whether it is in a multi-user collaboration state, the application-layer code based on BlockSuite should be unaware of it**.

We will introduce how this design is embodied in BlockSuite.
We will introduce how this is designed in BlockSuite.

## CRDT as Single Source of Truth

Expand Down Expand Up @@ -30,6 +43,33 @@ This design can be represented by the following diagram:

The advantage of this approach is that the application-layer code can **completely ignore whether updates to the block model come from local editing, history stack, or collaboration with other users**. Just subscribing to model update events is adequate.

## Case Study

As an example, suppose the current block tree structure is as follows:

```
PageBlock
NoteBlock
ParagraphBlock 0
ParagraphBlock 1
ParagraphBlock 2
```

Now user A selects `ParagraphBlock 2` and presses the delete key to delete it. At this point, `page.deleteBlock` should be called to delete this block model instance:

```ts
const blockModel = page.root.children[0].children[2];
page.deleteBlock(blockModel);
```

At this point, BlockSuite does not directly modify the block tree under page.root, but instead first modifies the underlying YBlock. After the CRDT state is changed, Yjs generates a corresponding [Y.Event](https://docs.yjs.dev/api/y.event) data structure, which contains all the incremental state changes in this update (similar to patches in git and virtual DOM). BlockSuite will always use this as the basis

At this point, BlockSuite does not directly modify the block tree under `page.root`, but will instead firstly modify the underlying YBlock. After the CRDT state is changed, Yjs will generate the corresponding `Y.Event`, which is similar to incremental patches in git and virtual DOM. BlockSuite will always use this as the basis to synchronize the block models, then trigger the corresponding slot events for UI updates.

In this example, as the parent of `ParagraphBlock 2`, the `model.childrenUpdated` slot event of `NoteBlock` will be triggered. This will enable the corresponding component in the UI framework component tree to refresh itself. Since each child block has an ID, this is very conducive to combining the common list key optimizations in UI frameworks, achieving on-demand block component updates.

But the real power lies in the fact that if this block tree is being concurrently edited by multiple people, when user B performs a similar operation, the corresponding update will be encoded by Yjs and distributed by the provider. **When User A receives and applies the update from User B, the same state update pipeline as local editing will be triggered**. This makes it unnecessary for the application to make any additional modifications or adaptations for collaboration scenarios, inherently gaining real-time collaboration capabilities.

## Unidirectional Update Flow

Besides the block tree that uses CRDT as its single source of truth, BlockSuite also manages shared states that do not require a history of changes, such as the awareness state of each user's cursor position. Additionally, some user metadata may not be shared among all users.
Expand Down
9 changes: 8 additions & 1 deletion packages/docs/blog/document-centric.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
---
layout: doc
title: Building Document-Centric, CRDT-Native Editors
date: 2024-01-10
authors:
- name: Yifeng Wang
link: 'https://twitter.com/ewind1994'
excerpt: 'This article presents the document-centric way for building editors, and why CRDT is required to make this happpen.'
---

# Building Document-Centric, CRDT-Native Editors

<BlogPostMeta />

## Motivation

For years, web frameworks such as React and Vue have popularized the mental model of component based development. This approach allows us to break down complex front-end applications into components for better composition and maintenance.
Expand Down
6 changes: 2 additions & 4 deletions packages/docs/blog/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
---
layout: doc
layout: BlogListLayout
title: Blog
---

- [Building Document-Centric, CRDT-Native Editors](./document-centric)
- [CRDT-Native Data Flow](./crdt-native-data-flow)
1 change: 1 addition & 0 deletions packages/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@blocksuite/blocks": "workspace:*",
"@blocksuite/presets": "workspace:*",
"@blocksuite/store": "workspace:*",
"date-fns": "^3.3.0",
"markdown-it-container": "^4.0.0",
"vitepress-plugin-sandpack": "^1.1.4"
}
Expand Down
4 changes: 4 additions & 0 deletions packages/docs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.json",
"include": ["./.vitepress"],
}
9 changes: 6 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 0e8fa4f

Please sign in to comment.