Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add pdf support #6143

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/blocks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"micromark-util-combine-extensions": "^2.0.0",
"nanoid": "^5.0.4",
"pdf-lib": "^1.17.1",
"pdfjs-dist": "^4.0.379",
"rehype-parse": "^9.0.0",
"rehype-stringify": "^10.0.0",
"remark-parse": "^11.0.0",
Expand Down
29 changes: 19 additions & 10 deletions packages/blocks/src/_common/components/file-drop-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,12 @@ export class FileDropManager {

const { clientX, clientY } = event;
const point = new Point(clientX, clientY);
const element = getClosestBlockElementByPoint(point.clone());
const element = getClosestBlockElementByPoint(
point.clone(),
undefined,
undefined,
['affine:surface']
);

let result: DropResult | null = null;
if (element) {
Expand All @@ -130,22 +135,26 @@ export class FileDropManager {
result = calcDropTarget(point, model, element);
}
}
if (result) {
FileDropManager._dropResult = result;
this._indicator.rect = result.rect;
} else {
FileDropManager._dropResult = null;
this._indicator.rect = null;
}

FileDropManager._dropResult = result ?? null;
this._setIndicatorRect(result);
};

onDragLeave = () => {
FileDropManager._dropResult = null;
this._indicator.rect = null;
this._setIndicatorRect(null);
};

private _setIndicatorRect = (result: DropResult | null) => {
if (result?.modelState.model.flavour === 'affine:surface') {
this._indicator.rect = null;
} else {
this._indicator.rect = result?.rect ?? null;
}
};

private _onDrop = (event: DragEvent) => {
this._indicator.rect = null;
this._setIndicatorRect(null);

const { onDrop } = this._fileDropOptions;
if (!onDrop) {
Expand Down
83 changes: 83 additions & 0 deletions packages/blocks/src/_common/utils/model.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,88 @@
import type { BlockModel, Doc } from '@blocksuite/store';

import type {
AttachmentBlockModel,
AttachmentBlockSchema,
BookmarkBlockModel,
BookmarkBlockSchema,
CodeBlockModel,
CodeBlockSchema,
DatabaseBlockModel,
DatabaseBlockSchema,
DataViewBlockModel,
DataViewBlockSchema,
DividerBlockModel,
DividerBlockSchema,
EmbedFigmaModel,
EmbedGithubModel,
EmbedHtmlModel,
EmbedLinkedDocModel,
EmbedLoomModel,
EmbedSyncedDocModel,
EmbedYoutubeModel,
FrameBlockModel,
FrameBlockSchema,
ImageBlockModel,
ImageBlockSchema,
ListBlockModel,
ListBlockSchema,
NoteBlockModel,
NoteBlockSchema,
ParagraphBlockModel,
ParagraphBlockSchema,
PDFBlockModel,
RootBlockModel,
RootBlockSchema,
SurfaceBlockModel,
SurfaceBlockSchema,
SurfaceRefBlockModel,
} from '../../index.js';

export type BlockModels = {
'affine:paragraph': ParagraphBlockModel;
'affine:page': RootBlockModel;
'affine:list': ListBlockModel;
'affine:note': NoteBlockModel;
'affine:code': CodeBlockModel;
'affine:divider': DividerBlockModel;
'affine:image': ImageBlockModel;
'affine:surface': SurfaceBlockModel;
'affine:frame': FrameBlockModel;
'affine:database': DatabaseBlockModel;
'affine:data-view': DataViewBlockModel;
'affine:bookmark': BookmarkBlockModel;
'affine:attachment': AttachmentBlockModel;
'affine:surface-ref': SurfaceRefBlockModel;
'affine:embed-github': EmbedGithubModel;
'affine:embed-youtube': EmbedYoutubeModel;
'affine:embed-figma': EmbedFigmaModel;
'affine:embed-linked-doc': EmbedLinkedDocModel;
'affine:embed-synced-doc': EmbedSyncedDocModel;
'affine:embed-html': EmbedHtmlModel;
'affine:pdf': PDFBlockModel;
'affine:embed-loom': EmbedLoomModel;
};

export type BlockSchemas = {
'affine:paragraph': typeof ParagraphBlockSchema;
'affine:page': typeof RootBlockSchema;
'affine:list': typeof ListBlockSchema;
'affine:note': typeof NoteBlockSchema;
'affine:code': typeof CodeBlockSchema;
'affine:divider': typeof DividerBlockSchema;
'affine:image': typeof ImageBlockSchema;
'affine:surface': typeof SurfaceBlockSchema;
'affine:frame': typeof FrameBlockSchema;
'affine:database': typeof DatabaseBlockSchema;
'affine:data-view': typeof DataViewBlockSchema;
'affine:bookmark': typeof BookmarkBlockSchema;
'affine:attachment': typeof AttachmentBlockSchema;
};

export type BlockModelProps = {
[K in keyof BlockSchemas]: ReturnType<BlockSchemas[K]['model']['props']>;
};

export function assertFlavours(model: { flavour: string }, allowed: string[]) {
if (!allowed.includes(model.flavour)) {
throw new Error(`model flavour ${model.flavour} is not allowed`);
Expand Down
57 changes: 26 additions & 31 deletions packages/blocks/src/_common/utils/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -418,13 +418,6 @@ function hasBlockId(element: Element): element is BlockComponent {
return element.hasAttribute(ATTR);
}

/**
* Returns `true` if element is doc page.
*/
function isDocPage({ tagName }: Element) {
return tagName === 'AFFINE-PAGE-ROOT';
}

/**
* Returns `true` if element is edgeless page.
*
Expand All @@ -437,33 +430,24 @@ export function isEdgelessPage(
}

/**
* Returns `true` if element is default/edgeless page or note.
* Returns `true` if a element is a block. It will exclude the note block, page block, and surface block by default.
* If you want to include them, you can pass their flavour in `includes` parameter.
*/
function isRootOrNoteOrSurface(element: Element) {
function isBlock(element: BlockComponent, includes: string[] = []) {
const exclude = ['affine:page', 'affine:note', 'affine:surface'];
const targetFlavour = element?.model?.flavour;

return (
isDocPage(element) ||
isEdgelessPage(element) ||
isNote(element) ||
isSurface(element)
targetFlavour &&
(exclude.every(flavour => targetFlavour !== flavour) ||
includes.some(flavour => targetFlavour === flavour))
);
}

function isBlock(element: BlockComponent) {
return !isRootOrNoteOrSurface(element);
}

function isImage({ tagName }: Element) {
return tagName === 'AFFINE-IMAGE';
}

function isNote({ tagName }: Element) {
return tagName === 'AFFINE-NOTE';
}

function isSurface({ tagName }: Element) {
return tagName === 'AFFINE-SURFACE';
}

function isDatabase({ tagName }: Element) {
return tagName === 'AFFINE-DATABASE-TABLE' || tagName === 'AFFINE-DATABASE';
}
Expand Down Expand Up @@ -505,7 +489,12 @@ export function getClosestBlockElementByPoint(
y: boolean;
};
} | null = null,
scale = 1
scale = 1,
/**
* by default, the note block, page block, and surface block are not included.
* If you want to include them, you can pass their flavour in `includes` parameter.
*/
includes = [] as string[]
): Element | null {
const { y } = point;

Expand Down Expand Up @@ -545,7 +534,8 @@ export function getClosestBlockElementByPoint(
// find block element
element = findBlockElement(
document.elementsFromPoint(point.x, point.y),
container
container,
includes
);

// Horizontal direction: for nested structures
Expand Down Expand Up @@ -597,7 +587,8 @@ export function getClosestBlockElementByPoint(
// find block element
element = findBlockElement(
document.elementsFromPoint(point.x, point.y),
container
container,
includes
);

if (element) {
Expand Down Expand Up @@ -726,7 +717,11 @@ export function getBlockElementsExcludeSubtrees(
* Find block element from an `Element[]`.
* In Chrome/Safari, `document.elementsFromPoint` does not include `affine-image`.
*/
function findBlockElement(elements: Element[], parent?: Element) {
function findBlockElement(
elements: Element[],
parent?: Element,
includes: string[] = []
) {
const len = elements.length;
let element = null;
let i = 0;
Expand All @@ -735,10 +730,10 @@ function findBlockElement(elements: Element[], parent?: Element) {
i++;
// if parent does not contain element, it's ignored
if (parent && !contains(parent, element)) continue;
if (hasBlockId(element) && isBlock(element)) return element;
if (hasBlockId(element) && isBlock(element, includes)) return element;
if (isImage(element)) {
const element = elements[i];
if (i < len && hasBlockId(element) && isBlock(element)) {
if (i < len && hasBlockId(element) && isBlock(element, includes)) {
return elements[i];
}
return getClosestBlockElementByElement(element);
Expand Down
11 changes: 10 additions & 1 deletion packages/blocks/src/_specs/_specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import { NoteService } from '../note-block/index.js';
import { NoteBlockSchema } from '../note-block/note-model.js';
import { ParagraphBlockSchema } from '../paragraph-block/paragraph-model.js';
import { ParagraphService } from '../paragraph-block/paragraph-service.js';
import { PDFBlockSchema } from '../pdf-block/pdf-model.js';
import { PDFService } from '../pdf-block/pdf-service.js';
import { EdgelessRootService } from '../root-block/edgeless/edgeless-root-service.js';
import {
type EdgelessRootBlockWidgetName,
Expand Down Expand Up @@ -189,6 +191,13 @@ const CommonFirstPartyBlockSpecs: BlockSpec[] = [
},
service: AttachmentService,
},
{
schema: PDFBlockSchema,
view: {
component: literal`affine-pdf`,
},
service: PDFService,
},
EmbedFigmaBlockSpec,
EmbedYoutubeBlockSpec,
EmbedGithubBlockSpec,
Expand All @@ -204,7 +213,7 @@ export const PageEditorBlockSpecs: BlockSpec[] = [
{
schema: SurfaceBlockSchema,
view: {
component: literal`affine-surface`,
component: literal`affine-doc-surface`,
},
service: SurfacePageService,
},
Expand Down
4 changes: 3 additions & 1 deletion packages/blocks/src/attachment-block/attachment-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,9 @@ export class AttachmentService extends BlockService<AttachmentBlockModel> {

// generic attachment block for all files except images
const attachmentFiles = files.filter(
file => !file.type.startsWith('image/')
file =>
!file.type.startsWith('image/') &&
!file.type.startsWith('application/pdf')
);

if (targetModel && !matchFlavours(targetModel, ['affine:surface'])) {
Expand Down
4 changes: 4 additions & 0 deletions packages/blocks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import './code-block/affine-code-line.js';
import './image-block/index.js';
import './database-block/index.js';
import './surface-ref-block/index.js';
import './pdf-block/index.js';

import { mindMap } from './_common/mind-map/index.js';
import { matchFlavours, Point } from './_common/utils/index.js';
Expand Down Expand Up @@ -84,6 +85,8 @@ export * from './list-block/index.js';
export * from './models.js';
export * from './note-block/index.js';
export * from './paragraph-block/index.js';
export * from './paragraph-block/index.js';
export { PDFService } from './pdf-block/pdf-service.js';
export { EdgelessComponentToolbar } from './root-block/edgeless/components/component-toolbar/component-toolbar.js';
export { EdgelessTemplatePanel } from './root-block/edgeless/components/toolbar/template/template-panel.js';
export type {
Expand All @@ -110,6 +113,7 @@ export {
TextElementModel,
} from './surface-block/index.js';
export { SurfaceBlockComponent } from './surface-block/surface-block.js';
export { SurfaceDocBlockComponent } from './surface-block/surface-doc-block.js';
export { SurfaceBlockSchema } from './surface-block/surface-model.js';
export * from './surface-block/surface-service.js';
export * from './surface-ref-block/index.js';
Expand Down
4 changes: 4 additions & 0 deletions packages/blocks/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import type { NoteBlockModel } from './note-block/note-model.js';
import { NoteBlockSchema } from './note-block/note-model.js';
import type { ParagraphBlockModel } from './paragraph-block/paragraph-model.js';
import { ParagraphBlockSchema } from './paragraph-block/paragraph-model.js';
import type { PDFBlockModel } from './pdf-block/pdf-model.js';
import { PDFBlockSchema } from './pdf-block/pdf-model.js';
import type { RootBlockModel } from './root-block/root-model.js';
import { RootBlockSchema } from './root-block/root-model.js';
import type { SurfaceBlockModel } from './surface-block/surface-model.js';
Expand All @@ -56,6 +58,7 @@ export type {
ListBlockModel,
NoteBlockModel,
ParagraphBlockModel,
PDFBlockModel,
RootBlockModel,
SurfaceBlockModel,
};
Expand All @@ -74,6 +77,7 @@ export const AffineSchemas: z.infer<typeof BlockSchema>[] = [
FrameBlockSchema,
DatabaseBlockSchema,
SurfaceRefBlockSchema,
PDFBlockSchema,
];

export const __unstableSchemas = [
Expand Down
1 change: 1 addition & 0 deletions packages/blocks/src/note-block/note-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const NoteBlockSchema = defineBlockSchema({
'affine:attachment',
'affine:surface-ref',
'affine:embed-*',
'affine:pdf',
],
},
toModel: () => {
Expand Down
Loading