mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-14 21:27:20 +00:00
feat(server): handle workspace doc updates (#11937)
This commit is contained in:
@@ -1,14 +1,17 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { OnEvent } from '../../base';
|
||||
import { JobQueue, OnEvent } from '../../base';
|
||||
import { Models } from '../../models';
|
||||
import { PgWorkspaceDocStorageAdapter } from './adapters/workspace';
|
||||
import { DocReader } from './reader';
|
||||
|
||||
@Injectable()
|
||||
export class DocEventsListener {
|
||||
constructor(
|
||||
private readonly docReader: DocReader,
|
||||
private readonly models: Models
|
||||
private readonly models: Models,
|
||||
private readonly workspace: PgWorkspaceDocStorageAdapter,
|
||||
private readonly queue: JobQueue
|
||||
) {}
|
||||
|
||||
@OnEvent('doc.snapshot.updated')
|
||||
@@ -26,6 +29,17 @@ export class DocEventsListener {
|
||||
return;
|
||||
}
|
||||
await this.models.doc.upsertMeta(workspaceId, docId, content);
|
||||
await this.queue.add(
|
||||
'indexer.indexDoc',
|
||||
{
|
||||
workspaceId,
|
||||
docId,
|
||||
},
|
||||
{
|
||||
jobId: `${workspaceId}/${docId}`,
|
||||
priority: 100,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
// update workspace content to database
|
||||
const content = this.docReader.parseWorkspaceContent(blob);
|
||||
@@ -33,6 +47,33 @@ export class DocEventsListener {
|
||||
return;
|
||||
}
|
||||
await this.models.workspace.update(workspaceId, content);
|
||||
await this.queue.add(
|
||||
'indexer.indexWorkspace',
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
{
|
||||
jobId: workspaceId,
|
||||
priority: 100,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@OnEvent('user.deleted')
|
||||
async clearUserWorkspaces(payload: Events['user.deleted']) {
|
||||
for (const workspace of payload.ownedWorkspaces) {
|
||||
await this.workspace.deleteSpace(workspace);
|
||||
await this.queue.add(
|
||||
'indexer.deleteWorkspace',
|
||||
{
|
||||
workspaceId: workspace,
|
||||
},
|
||||
{
|
||||
jobId: workspace,
|
||||
priority: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Cron, CronExpression } from '@nestjs/schedule';
|
||||
|
||||
import { JobQueue, OnEvent, OnJob } from '../../base';
|
||||
import { JobQueue, OnJob } from '../../base';
|
||||
import { Models } from '../../models';
|
||||
import { PgWorkspaceDocStorageAdapter } from './adapters/workspace';
|
||||
|
||||
declare global {
|
||||
interface Jobs {
|
||||
@@ -15,7 +14,6 @@ declare global {
|
||||
export class DocStorageCronJob {
|
||||
constructor(
|
||||
private readonly models: Models,
|
||||
private readonly workspace: PgWorkspaceDocStorageAdapter,
|
||||
private readonly queue: JobQueue
|
||||
) {}
|
||||
|
||||
@@ -34,11 +32,4 @@ export class DocStorageCronJob {
|
||||
async cleanExpiredHistories() {
|
||||
await this.models.history.cleanExpired();
|
||||
}
|
||||
|
||||
@OnEvent('user.deleted')
|
||||
async clearUserWorkspaces(payload: Events['user.deleted']) {
|
||||
for (const workspace of payload.ownedWorkspaces) {
|
||||
await this.workspace.deleteSpace(workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,644 @@
|
||||
# Snapshot report for `src/core/utils/__tests__/blocksute.spec.ts`
|
||||
|
||||
The actual snapshot is saved in `blocksute.spec.ts.snap`.
|
||||
|
||||
Generated by [AVA](https://avajs.dev).
|
||||
|
||||
## can read all doc ids from workspace snapshot
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
[
|
||||
'5nS9BSp3Px',
|
||||
]
|
||||
|
||||
## can read all blocks from doc snapshot
|
||||
|
||||
> Snapshot 1
|
||||
|
||||
{
|
||||
blocks: [
|
||||
{
|
||||
additional: {
|
||||
displayMode: 'edgeless',
|
||||
noteBlockId: undefined,
|
||||
},
|
||||
blockId: 'TnUgtVg7Eu',
|
||||
content: 'Write, Draw, Plan all at Once.',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:page',
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'RX4CG2zsBk',
|
||||
},
|
||||
blockId: 'FoPQcAyV_m',
|
||||
content: 'AFFiNE is an open source all in one workspace, an operating system for all the building blocks of your team wiki, knowledge management and digital assets and a better alternative to Notion and Miro. ',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'RX4CG2zsBk',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'RX4CG2zsBk',
|
||||
},
|
||||
blockId: 'oz48nn_zp8',
|
||||
content: '',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'RX4CG2zsBk',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'RX4CG2zsBk',
|
||||
},
|
||||
blockId: 'g8a-D9-jXS',
|
||||
content: 'You own your data, with no compromises',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'RX4CG2zsBk',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'RX4CG2zsBk',
|
||||
},
|
||||
blockId: 'J8lHN1GR_5',
|
||||
content: 'Local-first & Real-time collaborative',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'RX4CG2zsBk',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'RX4CG2zsBk',
|
||||
},
|
||||
blockId: 'xCuWdM0VLz',
|
||||
content: 'We love the idea proposed by Ink & Switch in the famous article about you owning your data, despite the cloud. Furthermore, AFFiNE is the first all-in-one workspace that keeps your data ownership with no compromises on real-time collaboration and editing experience.',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'RX4CG2zsBk',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'RX4CG2zsBk',
|
||||
},
|
||||
blockId: 'zElMi0tViK',
|
||||
content: 'AFFiNE is a local-first application upon CRDTs with real-time collaboration support. Your data is always stored locally while multiple nodes remain synced in real-time.',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'RX4CG2zsBk',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'RX4CG2zsBk',
|
||||
},
|
||||
blockId: 'Z4rK0OF9Wk',
|
||||
content: '',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'RX4CG2zsBk',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'S1mkc8zUoU',
|
||||
},
|
||||
blockId: 'DQ0Ryb-SpW',
|
||||
content: 'Blocks that assemble your next docs, tasks kanban or whiteboard',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'S1mkc8zUoU',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'yGlBdshAqN',
|
||||
},
|
||||
blockId: 'HAZC3URZp_',
|
||||
content: 'There is a large overlap of their atomic "building blocks" between these apps. They are neither open source nor have a plugin system like VS Code for contributors to customize. We want to have something that contains all the features we love and goes one step further. ',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'yGlBdshAqN',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'yGlBdshAqN',
|
||||
},
|
||||
blockId: '0H87ypiuv8',
|
||||
content: 'We are building AFFiNE to be a fundamental open source platform that contains all the building blocks for docs, task management and visual collaboration, hoping you can shape your next workflow with us that can make your life better and also connect others, too.',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'yGlBdshAqN',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'yGlBdshAqN',
|
||||
},
|
||||
blockId: 'Sp4G1KD0Wn',
|
||||
content: 'If you want to learn more about the product design of AFFiNE, here goes the concepts:',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'yGlBdshAqN',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'yGlBdshAqN',
|
||||
},
|
||||
blockId: 'RsUhDuEqXa',
|
||||
content: 'To Shape, not to adapt. AFFiNE is built for individuals & teams who care about their data, who refuse vendor lock-in, and who want to have control over their essential tools.',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'yGlBdshAqN',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: '6lDiuDqZGL',
|
||||
},
|
||||
blockId: 'Z2HibKzAr-',
|
||||
content: 'A true canvas for blocks in any form',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: '6lDiuDqZGL',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: '6lDiuDqZGL',
|
||||
},
|
||||
blockId: 'UwvWddamzM',
|
||||
content: 'Many editor apps claimed to be a canvas for productivity. Since the Mother of All Demos, Douglas Engelbart, a creative and programable digital workspace has been a pursuit and an ultimate mission for generations of tool makers. ',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: '6lDiuDqZGL',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: '6lDiuDqZGL',
|
||||
},
|
||||
blockId: 'g9xKUjhJj1',
|
||||
content: '',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: '6lDiuDqZGL',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: '6lDiuDqZGL',
|
||||
},
|
||||
blockId: 'wDTn4YJ4pm',
|
||||
content: '"We shape our tools and thereafter our tools shape us”. A lot of pioneers have inspired us a long the way, e.g.:',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: '6lDiuDqZGL',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: '6lDiuDqZGL',
|
||||
},
|
||||
blockId: 'xFrrdiP3-V',
|
||||
content: 'Quip & Notion with their great concept of "everything is a block"',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:list',
|
||||
parentBlockId: '6lDiuDqZGL',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: '6lDiuDqZGL',
|
||||
},
|
||||
blockId: 'Tp9xyN4Okl',
|
||||
content: 'Trello with their Kanban',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:list',
|
||||
parentBlockId: '6lDiuDqZGL',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: '6lDiuDqZGL',
|
||||
},
|
||||
blockId: 'K_4hUzKZFQ',
|
||||
content: 'Airtable & Miro with their no-code programable datasheets',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:list',
|
||||
parentBlockId: '6lDiuDqZGL',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: '6lDiuDqZGL',
|
||||
},
|
||||
blockId: 'QwMzON2s7x',
|
||||
content: 'Miro & Whimiscal with their edgeless visual whiteboard',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:list',
|
||||
parentBlockId: '6lDiuDqZGL',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: '6lDiuDqZGL',
|
||||
},
|
||||
blockId: 'FFVmit6u1T',
|
||||
content: 'Remnote & Capacities with their object-based tag system',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:list',
|
||||
parentBlockId: '6lDiuDqZGL',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'cauvaHOQmh',
|
||||
},
|
||||
blockId: 'YqnG5O6AE6',
|
||||
content: 'For more details, please refer to our RoadMap',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'cauvaHOQmh',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'cauvaHOQmh',
|
||||
},
|
||||
blockId: 'sbDTmZMZcq',
|
||||
content: 'Self Host',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'cauvaHOQmh',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'cauvaHOQmh',
|
||||
},
|
||||
blockId: 'QVvitesfbj',
|
||||
content: 'Self host AFFiNE',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'cauvaHOQmh',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: 'Learning From',
|
||||
displayMode: 'page',
|
||||
noteBlockId: '2jwCeO8Yot',
|
||||
},
|
||||
blockId: 'U_GoHFD9At',
|
||||
content: [
|
||||
'Learning From',
|
||||
'Title',
|
||||
'Tag',
|
||||
'Reference',
|
||||
'Developers',
|
||||
'AFFiNE',
|
||||
],
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:database',
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: 'Learning From',
|
||||
displayMode: 'page',
|
||||
noteBlockId: '2jwCeO8Yot',
|
||||
},
|
||||
blockId: 'tpyOZbPc1P',
|
||||
content: 'Affine Development',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'U_GoHFD9At',
|
||||
parentFlavour: 'affine:database',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: 'Learning From',
|
||||
displayMode: 'page',
|
||||
noteBlockId: '2jwCeO8Yot',
|
||||
},
|
||||
blockId: 'VMx9lHw3TR',
|
||||
content: 'For developers or installations guides, please go to AFFiNE Doc',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'U_GoHFD9At',
|
||||
parentFlavour: 'affine:database',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: 'Learning From',
|
||||
displayMode: 'page',
|
||||
noteBlockId: '2jwCeO8Yot',
|
||||
},
|
||||
blockId: 'Q6LnVyKoGS',
|
||||
content: 'Quip & Notion with their great concept of "everything is a block"',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'U_GoHFD9At',
|
||||
parentFlavour: 'affine:database',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: 'Learning From',
|
||||
displayMode: 'page',
|
||||
noteBlockId: '2jwCeO8Yot',
|
||||
},
|
||||
blockId: 'EkFHpB-mJi',
|
||||
content: 'Trello with their Kanban',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'U_GoHFD9At',
|
||||
parentFlavour: 'affine:database',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: 'Learning From',
|
||||
displayMode: 'page',
|
||||
noteBlockId: '2jwCeO8Yot',
|
||||
},
|
||||
blockId: '3aMlphe2lp',
|
||||
content: 'Airtable & Miro with their no-code programable datasheets',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'U_GoHFD9At',
|
||||
parentFlavour: 'affine:database',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: 'Learning From',
|
||||
displayMode: 'page',
|
||||
noteBlockId: '2jwCeO8Yot',
|
||||
},
|
||||
blockId: 'MiZtUig-fL',
|
||||
content: 'Miro & Whimiscal with their edgeless visual whiteboard',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'U_GoHFD9At',
|
||||
parentFlavour: 'affine:database',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: 'Learning From',
|
||||
displayMode: 'page',
|
||||
noteBlockId: '2jwCeO8Yot',
|
||||
},
|
||||
blockId: 'erYE2C7cc5',
|
||||
content: 'Remnote & Capacities with their object-based tag system',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'U_GoHFD9At',
|
||||
parentFlavour: 'affine:database',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'c9MF_JiRgx',
|
||||
},
|
||||
blockId: 'NyHXrMX3R1',
|
||||
content: 'Affine Development',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'c9MF_JiRgx',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'c9MF_JiRgx',
|
||||
},
|
||||
blockId: '9-K49otbCv',
|
||||
content: 'For developer or installation guides, please go to AFFiNE Development',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'c9MF_JiRgx',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
databaseName: undefined,
|
||||
displayMode: 'page',
|
||||
noteBlockId: 'c9MF_JiRgx',
|
||||
},
|
||||
blockId: 'faFteK9eG-',
|
||||
content: '',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:paragraph',
|
||||
parentBlockId: 'c9MF_JiRgx',
|
||||
parentFlavour: 'affine:note',
|
||||
ref: [],
|
||||
refDocId: [],
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
displayMode: 'edgeless',
|
||||
noteBlockId: undefined,
|
||||
},
|
||||
blockId: '6x7ALjUDjj',
|
||||
content: [
|
||||
'What is AFFiNE',
|
||||
'Related Articles',
|
||||
' ',
|
||||
'Self-host',
|
||||
'',
|
||||
'AFFiNE ',
|
||||
'Development',
|
||||
'You can check these URLs to learn about AFFiNE',
|
||||
'Database Reference',
|
||||
],
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:surface',
|
||||
parentBlockId: 'TnUgtVg7Eu',
|
||||
parentFlavour: 'affine:page',
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
displayMode: 'edgeless',
|
||||
noteBlockId: undefined,
|
||||
},
|
||||
blockId: 'ECrtbvW6xx',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:bookmark',
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
displayMode: 'edgeless',
|
||||
noteBlockId: undefined,
|
||||
},
|
||||
blockId: '5W--UQLN11',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:bookmark',
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
displayMode: 'edgeless',
|
||||
noteBlockId: undefined,
|
||||
},
|
||||
blob: [
|
||||
'BFZk3c2ERp-sliRvA7MQ_p3NdkdCLt2Ze0DQ9i21dpA=',
|
||||
],
|
||||
blockId: 'lcZphIJe63',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:image',
|
||||
parentBlockId: '6x7ALjUDjj',
|
||||
parentFlavour: 'affine:surface',
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
displayMode: 'edgeless',
|
||||
noteBlockId: undefined,
|
||||
},
|
||||
blob: [
|
||||
'HWvCItS78DzPGbwcuaGcfkpVDUvL98IvH5SIK8-AcL8=',
|
||||
],
|
||||
blockId: 'JlgVJdWU12',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:image',
|
||||
parentBlockId: '6x7ALjUDjj',
|
||||
parentFlavour: 'affine:surface',
|
||||
},
|
||||
{
|
||||
additional: {
|
||||
displayMode: 'edgeless',
|
||||
noteBlockId: undefined,
|
||||
},
|
||||
blob: [
|
||||
'ZRKpsBoC88qEMmeiXKXqywfA1rLvWoLa5rpEh9x9Oj0=',
|
||||
],
|
||||
blockId: 'lht7AqBqnF',
|
||||
docId: 'doc-0',
|
||||
flavour: 'affine:image',
|
||||
parentBlockId: '6x7ALjUDjj',
|
||||
parentFlavour: 'affine:surface',
|
||||
},
|
||||
],
|
||||
summary: 'AFFiNE is an open source all in one workspace, an operating system for all the building blocks of your team wiki, knowledge management and digital assets and a better alternative to Notion and Miro. You own your data, with no compromisesLocal-first & Real-time collaborativeWe love the idea proposed by Ink & Switch in the famous article about you owning your data, despite the cloud. Furthermore, AFFiNE is the first all-in-one workspace that keeps your data ownership with no compromises on real-time collaboration and editing experience.AFFiNE is a local-first application upon CRDTs with real-time collaboration support. Your data is always stored locally while multiple nodes remain synced in real-time.Blocks that assemble your next docs, tasks kanban or whiteboardThere is a large overlap of their atomic "building blocks" between these apps. They are neither open source nor have a plugin system like VS Code for contributors to customize. We want to have something that contains all the features we love and goes one step further. ',
|
||||
title: 'Write, Draw, Plan all at Once.',
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,57 @@
|
||||
import test from 'ava';
|
||||
import { omit } from 'lodash-es';
|
||||
|
||||
import { createModule } from '../../../__tests__/create-module';
|
||||
import { Mockers } from '../../../__tests__/mocks';
|
||||
import { Models } from '../../../models';
|
||||
import {
|
||||
readAllBlocksFromDocSnapshot,
|
||||
readAllDocIdsFromWorkspaceSnapshot,
|
||||
} from '../blocksuite';
|
||||
|
||||
const module = await createModule({});
|
||||
const models = module.get(Models);
|
||||
|
||||
const owner = await module.create(Mockers.User);
|
||||
const workspace = await module.create(Mockers.Workspace, {
|
||||
snapshot: true,
|
||||
owner,
|
||||
});
|
||||
|
||||
const docSnapshot = await module.create(Mockers.DocSnapshot, {
|
||||
workspaceId: workspace.id,
|
||||
user: owner,
|
||||
});
|
||||
|
||||
test.after.always(async () => {
|
||||
await module.close();
|
||||
});
|
||||
|
||||
test('can read all doc ids from workspace snapshot', async t => {
|
||||
const rootDoc = await models.doc.get(workspace.id, workspace.id);
|
||||
t.truthy(rootDoc);
|
||||
|
||||
const docIds = readAllDocIdsFromWorkspaceSnapshot(rootDoc!.blob);
|
||||
|
||||
t.deepEqual(docIds, ['5nS9BSp3Px']);
|
||||
t.snapshot(docIds);
|
||||
});
|
||||
|
||||
test('can read all blocks from doc snapshot', async t => {
|
||||
const rootDoc = await models.doc.get(workspace.id, workspace.id);
|
||||
t.truthy(rootDoc);
|
||||
const doc = await models.doc.get(workspace.id, docSnapshot.id);
|
||||
t.truthy(doc);
|
||||
|
||||
const result = await readAllBlocksFromDocSnapshot(
|
||||
workspace.id,
|
||||
rootDoc!.blob,
|
||||
'doc-0',
|
||||
docSnapshot.blob
|
||||
);
|
||||
|
||||
t.snapshot({
|
||||
...result,
|
||||
blocks: result!.blocks.map(block => omit(block, ['yblock'])),
|
||||
});
|
||||
});
|
||||
@@ -1,12 +1,17 @@
|
||||
// TODO(@forehalo):
|
||||
// Because of the `@affine/server` package can't import directly from workspace packages,
|
||||
// this is a temprory solution to get the block suite data(title, description) from given yjs binary or yjs doc.
|
||||
// this is a temporary solution to get the block suite data(title, description) from given yjs binary or yjs doc.
|
||||
// The logic is mainly copied from
|
||||
// - packages/frontend/core/src/modules/docs-search/worker/in-worker.ts
|
||||
// - packages/frontend/core/src/components/page-list/use-block-suite-page-preview.ts
|
||||
// and it's better to be provided by blocksuite
|
||||
|
||||
import { Array, Doc, Map } from 'yjs';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports -- import from bundle
|
||||
import {
|
||||
readAllBlocksFromDoc,
|
||||
readAllDocIdsFromRootDoc,
|
||||
} from '@affine/reader/dist';
|
||||
import { applyUpdate, Array as YArray, Doc as YDoc, Map as YMap } from 'yjs';
|
||||
|
||||
export interface PageDocContent {
|
||||
title: string;
|
||||
@@ -31,7 +36,7 @@ type KnownFlavour =
|
||||
| 'affine:callout'
|
||||
| 'affine:table';
|
||||
|
||||
export function parseWorkspaceDoc(doc: Doc): WorkspaceDocContent | null {
|
||||
export function parseWorkspaceDoc(doc: YDoc): WorkspaceDocContent | null {
|
||||
// not a workspace doc
|
||||
if (!doc.share.has('meta')) {
|
||||
return null;
|
||||
@@ -50,7 +55,7 @@ export interface ParsePageOptions {
|
||||
}
|
||||
|
||||
export function parsePageDoc(
|
||||
doc: Doc,
|
||||
doc: YDoc,
|
||||
opts: ParsePageOptions = { maxSummaryLength: 150 }
|
||||
): PageDocContent | null {
|
||||
// not a page doc
|
||||
@@ -58,7 +63,7 @@ export function parsePageDoc(
|
||||
return null;
|
||||
}
|
||||
|
||||
const blocks = doc.getMap<Map<any>>('blocks');
|
||||
const blocks = doc.getMap<YMap<any>>('blocks');
|
||||
|
||||
if (!blocks.size) {
|
||||
return null;
|
||||
@@ -71,7 +76,7 @@ export function parsePageDoc(
|
||||
|
||||
let summaryLenNeeded = opts.maxSummaryLength;
|
||||
|
||||
let root: Map<any> | null = null;
|
||||
let root: YMap<any> | null = null;
|
||||
for (const block of blocks.values()) {
|
||||
const flavour = block.get('sys:flavour') as KnownFlavour;
|
||||
if (flavour === 'affine:page') {
|
||||
@@ -86,8 +91,8 @@ export function parsePageDoc(
|
||||
|
||||
const queue: string[] = [root.get('sys:id')];
|
||||
|
||||
function pushChildren(block: Map<any>) {
|
||||
const children = block.get('sys:children') as Array<string> | undefined;
|
||||
function pushChildren(block: YMap<any>) {
|
||||
const children = block.get('sys:children') as YArray<string> | undefined;
|
||||
if (children?.length) {
|
||||
for (let i = children.length - 1; i >= 0; i--) {
|
||||
queue.push(children.get(i));
|
||||
@@ -157,3 +162,34 @@ export function parsePageDoc(
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
export function readAllDocIdsFromWorkspaceSnapshot(snapshot: Uint8Array) {
|
||||
const rootDoc = new YDoc();
|
||||
applyUpdate(rootDoc, snapshot);
|
||||
return readAllDocIdsFromRootDoc(rootDoc, {
|
||||
includeTrash: false,
|
||||
});
|
||||
}
|
||||
|
||||
export async function readAllBlocksFromDocSnapshot(
|
||||
workspaceId: string,
|
||||
workspaceSnapshot: Uint8Array,
|
||||
docId: string,
|
||||
docSnapshot: Uint8Array,
|
||||
maxSummaryLength?: number
|
||||
) {
|
||||
const rootYDoc = new YDoc({
|
||||
guid: workspaceId,
|
||||
});
|
||||
applyUpdate(rootYDoc, workspaceSnapshot);
|
||||
const ydoc = new YDoc({
|
||||
guid: docId,
|
||||
});
|
||||
applyUpdate(ydoc, docSnapshot);
|
||||
return await readAllBlocksFromDoc({
|
||||
ydoc,
|
||||
rootYDoc,
|
||||
spaceId: workspaceId,
|
||||
maxSummaryLength,
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user