toeverything / blocksuite

🧩 Content editing tech stack for the web - BlockSuite is a toolkit for building editors and collaborative applications.
https://blocksuite.io
Mozilla Public License 2.0
4.35k stars 398 forks source link

[Image block] throw "Error:Image blob is missing!" after Y.encodeStateAsUpdate(doc.spaceDoc) #7298

Closed higuaifan closed 2 months ago

higuaifan commented 3 months ago

Hi, guys. As I said in my previous issue, I'm revamping an shuimo style Editor, based on blocksuite, and I'm thankful for your open source!

Now I have a new problem:

My scene is like this: After I paste an image into the editor, I get the doc's Uint8Array format data via:

uint8Array =  Y.encodeStateAsUpdate(doc.spaceDoc)

then I turn it into binary data and save uint8Array in the database, and when I read it out of the database, I use :

Y. applyUpdate(doc.spaceDoc,uint8Array)

to read it out.

So far I've tested a few base modules and they all show up normally, except for the image which prompts Error: Image blob is missing!

image

This scenario can be reproduced when using native blocksuite, so I can rule out that it's a problem with my packaging. I tried to check some data, and it seems that image block sourceId can not be found.

I would like to ask if there is something wrong with the way I am saving and reading the doc? If so, which is the right way to do it? Or how do I save the image correctly so I can subsequently read it out successfully?

Thanks for your help! I can try to provide a minimal demo if needed.

防英语表达不准确,这是中文版 你们好。 正如我在上个issue中所说,我正在改造一个基于`blocksuite`的水墨风编辑器,很感谢你们的开源! 现在我遇到了一个新问题: 我的场景是这样的: 将图片粘贴到编辑器后,我通过以下方式获取文档的 `Uint8Array` 格式数据: ```ts uint8Array = Y.encodeStateAsUpdate(doc.spaceDoc) ``` 然后,我将其转换成二进制数据,并将 `uint8Array` 保存在数据库中,当我从数据库中读取时,我使用 : ```ts Y. applyUpdate(doc.spaceDoc,uint8Array) ``` 来读取出来。 到目前为止,我已经测试了几个基础模块,它们都显示正常, 除了图像会提示 `Error: Image blob is missing!` image 使用原生的`blocksuite`时也会出现这种情况, 因此我可以排除是我的打包出现了问题。 我试着检查了一些数据,似乎是找不到图片块的`sourceId`。 我想问一下,我保存和读取文档的方法是否有误? 如果是,正确的方法是什么? 或者说,我该如何正确保存图片,以便我后续可以成功读取出来? 谢谢您的帮助! 如果需要,我可以尝试提供一个最简单的演示。
raintoway commented 3 months ago

this file will help u a lot

import { createMemoryStorage } from '../../persistence/blob/memory-storage.js';
import type { BlobManager, BlobStorage } from '../../persistence/blob/types.js';
import { sha } from '../../persistence/blob/utils.js';
import type { DocCollectionOptions } from '../collection.js';
import { addOnFactory } from './shared.js';

export interface BlobAddon {
  get blob(): BlobManager;
}

export const blob = addOnFactory<keyof BlobAddon>(
  originalClass =>
    class extends originalClass {
      private readonly _storages: BlobStorage[] = [];

      readonly blob: BlobManager;

      constructor(storeOptions: DocCollectionOptions) {
        super(storeOptions);

        this._storages = (
          storeOptions.blobStorages ?? [createMemoryStorage]
        ).map(fn => fn(storeOptions.id || ''));

        this.blob = {
          get: async id => {
            let found = false;
            let count = 0;
            return new Promise(res => {
              void this._storages.map(async storage => {
                try {
                  const blob = await storage.crud.get(id);
                  if (blob && !found) {
                    found = true;
                    res(blob);
                  }
                  if (++count === this._storages.length && !found) {
                    res(null);
                  }
                } catch (e) {
                  console.error(e);
                  if (++count === this._storages.length && !found) {
                    res(null);
                  }
                }
              });
            });
          },
          set: async (value, key) => {
            const _key = key || (await sha(await value.arrayBuffer()));
            await Promise.all(this._storages.map(s => s.crud.set(_key, value)));
            return _key;
          },
          delete: async key => {
            await Promise.all(this._storages.map(s => s.crud.delete(key)));
          },
          list: async () => {
            const keys = new Set<string>();
            await Promise.all(
              this._storages.map(async s => {
                const list = await s.crud.list();
                list.forEach(key => keys.add(key));
              })
            );
            return Array.from(keys);
          },
        };
      }
    }
);

this._storages is indexdb or cloud db

higuaifan commented 2 months ago

@raintoway Thank you very much for your help! This code really inspired me, but due to the rapid iteration of blocksuite, it doesn't work anymore. I found this commit #6937 and implemented what I needed after spending a bit of time on it and reading some source code. If anyone who comes after me has a similar need. Collection->blobSync->BlobEngine some of the source code under this code chain may give you some hack ideas.

Of course, I think documentation and some best practices are necessary, ImageBlock is a bit too black-box now, due to some keyword search differences, I may not be able to find the relevant content in a large number of issues without help I may need to spend a lot of time to solve this problem. @doodlewind Hopefully the documentation will be improved soon! BTW, thanks for the open source!