feat: support file block preview on web (#6081)

This commit is contained in:
Kilu.He 2024-08-27 15:01:23 +08:00 committed by GitHub
parent 40e627c303
commit d25efba292
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 366 additions and 131 deletions

View File

@ -34,6 +34,7 @@ export enum BlockType {
TableBlock = 'table', TableBlock = 'table',
TableCell = 'table/cell', TableCell = 'table/cell',
LinkPreview = 'link_preview', LinkPreview = 'link_preview',
FileBlock = 'file',
} }
export enum InlineBlockType { export enum InlineBlockType {
@ -85,6 +86,18 @@ export interface LinkPreviewBlockData extends BlockData {
url?: string; url?: string;
} }
export enum FieldURLType {
Upload = 2,
Link = 1,
}
export interface FileBlockData extends BlockData {
name: string;
uploaded_at: number;
url: string;
url_type: FieldURLType;
}
export enum ImageType { export enum ImageType {
Local = 0, Local = 0,
Internal = 1, Internal = 1,
@ -271,151 +284,151 @@ export enum YjsDatabaseKey {
export interface YDoc extends Y.Doc { export interface YDoc extends Y.Doc {
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
getMap(key: YjsEditorKey.data_section): YSharedRoot | any; getMap (key: YjsEditorKey.data_section): YSharedRoot | any;
} }
export interface YDatabaseRow extends Y.Map<unknown> { export interface YDatabaseRow extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): RowId; get (key: YjsDatabaseKey.id): RowId;
get(key: YjsDatabaseKey.height): string; get (key: YjsDatabaseKey.height): string;
get(key: YjsDatabaseKey.visibility): boolean; get (key: YjsDatabaseKey.visibility): boolean;
get(key: YjsDatabaseKey.created_at): CreatedAt; get (key: YjsDatabaseKey.created_at): CreatedAt;
get(key: YjsDatabaseKey.last_modified): LastModified; get (key: YjsDatabaseKey.last_modified): LastModified;
get(key: YjsDatabaseKey.cells): YDatabaseCells; get (key: YjsDatabaseKey.cells): YDatabaseCells;
} }
export interface YDatabaseCells extends Y.Map<unknown> { export interface YDatabaseCells extends Y.Map<unknown> {
get(key: FieldId): YDatabaseCell; get (key: FieldId): YDatabaseCell;
} }
export type EndTimestamp = string; export type EndTimestamp = string;
export type ReminderId = string; export type ReminderId = string;
export interface YDatabaseCell extends Y.Map<unknown> { export interface YDatabaseCell extends Y.Map<unknown> {
get(key: YjsDatabaseKey.created_at): CreatedAt; get (key: YjsDatabaseKey.created_at): CreatedAt;
get(key: YjsDatabaseKey.last_modified): LastModified; get (key: YjsDatabaseKey.last_modified): LastModified;
get(key: YjsDatabaseKey.field_type): string; get (key: YjsDatabaseKey.field_type): string;
get(key: YjsDatabaseKey.data): object | string | boolean | number; get (key: YjsDatabaseKey.data): object | string | boolean | number;
get(key: YjsDatabaseKey.end_timestamp): EndTimestamp; get (key: YjsDatabaseKey.end_timestamp): EndTimestamp;
get(key: YjsDatabaseKey.include_time): boolean; get (key: YjsDatabaseKey.include_time): boolean;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.is_range): boolean; get (key: YjsDatabaseKey.is_range): boolean;
get(key: YjsDatabaseKey.reminder_id): ReminderId; get (key: YjsDatabaseKey.reminder_id): ReminderId;
} }
export interface YSharedRoot extends Y.Map<unknown> { export interface YSharedRoot extends Y.Map<unknown> {
get(key: YjsEditorKey.document): YDocument; get (key: YjsEditorKey.document): YDocument;
get(key: YjsEditorKey.folder): YFolder; get (key: YjsEditorKey.folder): YFolder;
get(key: YjsEditorKey.database): YDatabase; get (key: YjsEditorKey.database): YDatabase;
get(key: YjsEditorKey.database_row): YDatabaseRow; get (key: YjsEditorKey.database_row): YDatabaseRow;
} }
export interface YFolder extends Y.Map<unknown> { export interface YFolder extends Y.Map<unknown> {
get(key: YjsFolderKey.views): YViews; get (key: YjsFolderKey.views): YViews;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.meta): YFolderMeta; get (key: YjsFolderKey.meta): YFolderMeta;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.relation): YFolderRelation; get (key: YjsFolderKey.relation): YFolderRelation;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.section): YFolderSection; get (key: YjsFolderKey.section): YFolderSection;
} }
export interface YViews extends Y.Map<unknown> { export interface YViews extends Y.Map<unknown> {
get(key: ViewId): YView; get (key: ViewId): YView;
} }
export interface YView extends Y.Map<unknown> { export interface YView extends Y.Map<unknown> {
get(key: YjsFolderKey.id): ViewId; get (key: YjsFolderKey.id): ViewId;
get(key: YjsFolderKey.bid): string; get (key: YjsFolderKey.bid): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.name): string; get (key: YjsFolderKey.name): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.icon | YjsFolderKey.extra): string; get (key: YjsFolderKey.icon | YjsFolderKey.extra): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsFolderKey.layout): string; get (key: YjsFolderKey.layout): string;
} }
export interface YFolderRelation extends Y.Map<unknown> { export interface YFolderRelation extends Y.Map<unknown> {
get(key: ViewId): Y.Array<ViewId>; get (key: ViewId): Y.Array<ViewId>;
} }
export interface YFolderMeta extends Y.Map<unknown> { export interface YFolderMeta extends Y.Map<unknown> {
get(key: YjsFolderKey.current_view | YjsFolderKey.current_workspace): string; get (key: YjsFolderKey.current_view | YjsFolderKey.current_workspace): string;
} }
export interface YFolderSection extends Y.Map<unknown> { export interface YFolderSection extends Y.Map<unknown> {
get(key: YjsFolderKey.favorite | YjsFolderKey.private | YjsFolderKey.recent | YjsFolderKey.trash): YFolderSectionItem; get (key: YjsFolderKey.favorite | YjsFolderKey.private | YjsFolderKey.recent | YjsFolderKey.trash): YFolderSectionItem;
} }
export interface YFolderSectionItem extends Y.Map<unknown> { export interface YFolderSectionItem extends Y.Map<unknown> {
get(key: string): Y.Array<unknown>; get (key: string): Y.Array<unknown>;
} }
export interface YDocument extends Y.Map<unknown> { export interface YDocument extends Y.Map<unknown> {
get(key: YjsEditorKey.blocks | YjsEditorKey.page_id | YjsEditorKey.meta): YBlocks | YMeta | string; get (key: YjsEditorKey.blocks | YjsEditorKey.page_id | YjsEditorKey.meta): YBlocks | YMeta | string;
} }
export interface YBlocks extends Y.Map<unknown> { export interface YBlocks extends Y.Map<unknown> {
get(key: BlockId): YBlock; get (key: BlockId): YBlock;
} }
export interface YBlock extends Y.Map<unknown> { export interface YBlock extends Y.Map<unknown> {
get(key: YjsEditorKey.block_id | YjsEditorKey.block_parent): BlockId; get (key: YjsEditorKey.block_id | YjsEditorKey.block_parent): BlockId;
get(key: YjsEditorKey.block_type): BlockType; get (key: YjsEditorKey.block_type): BlockType;
get(key: YjsEditorKey.block_data): string; get (key: YjsEditorKey.block_data): string;
get(key: YjsEditorKey.block_children): ChildrenId; get (key: YjsEditorKey.block_children): ChildrenId;
get(key: YjsEditorKey.block_external_id): ExternalId; get (key: YjsEditorKey.block_external_id): ExternalId;
} }
export interface YMeta extends Y.Map<unknown> { export interface YMeta extends Y.Map<unknown> {
get(key: YjsEditorKey.children_map | YjsEditorKey.text_map): YChildrenMap | YTextMap; get (key: YjsEditorKey.children_map | YjsEditorKey.text_map): YChildrenMap | YTextMap;
} }
export interface YChildrenMap extends Y.Map<unknown> { export interface YChildrenMap extends Y.Map<unknown> {
get(key: ChildrenId): Y.Array<BlockId>; get (key: ChildrenId): Y.Array<BlockId>;
} }
export interface YTextMap extends Y.Map<unknown> { export interface YTextMap extends Y.Map<unknown> {
get(key: ExternalId): Y.Text; get (key: ExternalId): Y.Text;
} }
export interface YDatabase extends Y.Map<unknown> { export interface YDatabase extends Y.Map<unknown> {
get(key: YjsDatabaseKey.views): YDatabaseViews; get (key: YjsDatabaseKey.views): YDatabaseViews;
get(key: YjsDatabaseKey.metas): YDatabaseMetas; get (key: YjsDatabaseKey.metas): YDatabaseMetas;
get(key: YjsDatabaseKey.fields): YDatabaseFields; get (key: YjsDatabaseKey.fields): YDatabaseFields;
get(key: YjsDatabaseKey.id): string; get (key: YjsDatabaseKey.id): string;
} }
export interface YDatabaseViews extends Y.Map<YDatabaseView> { export interface YDatabaseViews extends Y.Map<YDatabaseView> {
get(key: ViewId): YDatabaseView; get (key: ViewId): YDatabaseView;
} }
export type DatabaseId = string; export type DatabaseId = string;
@ -431,32 +444,32 @@ export enum DatabaseViewLayout {
} }
export interface YDatabaseView extends Y.Map<unknown> { export interface YDatabaseView extends Y.Map<unknown> {
get(key: YjsDatabaseKey.database_id): DatabaseId; get (key: YjsDatabaseKey.database_id): DatabaseId;
get(key: YjsDatabaseKey.name): string; get (key: YjsDatabaseKey.name): string;
get(key: YjsDatabaseKey.created_at): CreatedAt; get (key: YjsDatabaseKey.created_at): CreatedAt;
get(key: YjsDatabaseKey.modified_at): ModifiedAt; get (key: YjsDatabaseKey.modified_at): ModifiedAt;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.layout): string; get (key: YjsDatabaseKey.layout): string;
get(key: YjsDatabaseKey.layout_settings): YDatabaseLayoutSettings; get (key: YjsDatabaseKey.layout_settings): YDatabaseLayoutSettings;
get(key: YjsDatabaseKey.filters): YDatabaseFilters; get (key: YjsDatabaseKey.filters): YDatabaseFilters;
get(key: YjsDatabaseKey.groups): YDatabaseGroups; get (key: YjsDatabaseKey.groups): YDatabaseGroups;
get(key: YjsDatabaseKey.sorts): YDatabaseSorts; get (key: YjsDatabaseKey.sorts): YDatabaseSorts;
get(key: YjsDatabaseKey.field_settings): YDatabaseFieldSettings; get (key: YjsDatabaseKey.field_settings): YDatabaseFieldSettings;
get(key: YjsDatabaseKey.field_orders): YDatabaseFieldOrders; get (key: YjsDatabaseKey.field_orders): YDatabaseFieldOrders;
get(key: YjsDatabaseKey.row_orders): YDatabaseRowOrders; get (key: YjsDatabaseKey.row_orders): YDatabaseRowOrders;
get(key: YjsDatabaseKey.calculations): YDatabaseCalculations; get (key: YjsDatabaseKey.calculations): YDatabaseCalculations;
} }
export type YDatabaseFieldOrders = Y.Array<unknown>; // [ { id: FieldId } ] export type YDatabaseFieldOrders = Y.Array<unknown>; // [ { id: FieldId } ]
@ -477,128 +490,128 @@ export type GroupId = string;
export interface YDatabaseLayoutSettings extends Y.Map<unknown> { export interface YDatabaseLayoutSettings extends Y.Map<unknown> {
// DatabaseViewLayout.Board // DatabaseViewLayout.Board
get(key: '1'): YDatabaseBoardLayoutSetting; get (key: '1'): YDatabaseBoardLayoutSetting;
// DatabaseViewLayout.Calendar // DatabaseViewLayout.Calendar
get(key: '2'): YDatabaseCalendarLayoutSetting; get (key: '2'): YDatabaseCalendarLayoutSetting;
} }
export interface YDatabaseBoardLayoutSetting extends Y.Map<unknown> { export interface YDatabaseBoardLayoutSetting extends Y.Map<unknown> {
get(key: YjsDatabaseKey.hide_ungrouped_column | YjsDatabaseKey.collapse_hidden_groups): boolean; get (key: YjsDatabaseKey.hide_ungrouped_column | YjsDatabaseKey.collapse_hidden_groups): boolean;
} }
export interface YDatabaseCalendarLayoutSetting extends Y.Map<unknown> { export interface YDatabaseCalendarLayoutSetting extends Y.Map<unknown> {
get(key: YjsDatabaseKey.first_day_of_week | YjsDatabaseKey.field_id | YjsDatabaseKey.layout_ty): string; get (key: YjsDatabaseKey.first_day_of_week | YjsDatabaseKey.field_id | YjsDatabaseKey.layout_ty): string;
get(key: YjsDatabaseKey.show_week_numbers | YjsDatabaseKey.show_weekends): boolean; get (key: YjsDatabaseKey.show_week_numbers | YjsDatabaseKey.show_weekends): boolean;
} }
export interface YDatabaseGroup extends Y.Map<unknown> { export interface YDatabaseGroup extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): GroupId; get (key: YjsDatabaseKey.id): GroupId;
get(key: YjsDatabaseKey.field_id): FieldId; get (key: YjsDatabaseKey.field_id): FieldId;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.content): string; get (key: YjsDatabaseKey.content): string;
get(key: YjsDatabaseKey.groups): YDatabaseGroupColumns; get (key: YjsDatabaseKey.groups): YDatabaseGroupColumns;
} }
export type YDatabaseGroupColumns = Y.Array<YDatabaseGroupColumn>; export type YDatabaseGroupColumns = Y.Array<YDatabaseGroupColumn>;
export interface YDatabaseGroupColumn extends Y.Map<unknown> { export interface YDatabaseGroupColumn extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): string; get (key: YjsDatabaseKey.id): string;
get(key: YjsDatabaseKey.visible): boolean; get (key: YjsDatabaseKey.visible): boolean;
} }
export interface YDatabaseRowOrder extends Y.Map<unknown> { export interface YDatabaseRowOrder extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): SortId; get (key: YjsDatabaseKey.id): SortId;
get(key: YjsDatabaseKey.height): number; get (key: YjsDatabaseKey.height): number;
} }
export interface YDatabaseSort extends Y.Map<unknown> { export interface YDatabaseSort extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): SortId; get (key: YjsDatabaseKey.id): SortId;
get(key: YjsDatabaseKey.field_id): FieldId; get (key: YjsDatabaseKey.field_id): FieldId;
get(key: YjsDatabaseKey.condition): string; get (key: YjsDatabaseKey.condition): string;
} }
export type FilterId = string; export type FilterId = string;
export interface YDatabaseFilter extends Y.Map<unknown> { export interface YDatabaseFilter extends Y.Map<unknown> {
get(key: YjsDatabaseKey.id): FilterId; get (key: YjsDatabaseKey.id): FilterId;
get(key: YjsDatabaseKey.field_id): FieldId; get (key: YjsDatabaseKey.field_id): FieldId;
get(key: YjsDatabaseKey.type | YjsDatabaseKey.condition | YjsDatabaseKey.content | YjsDatabaseKey.filter_type): string; get (key: YjsDatabaseKey.type | YjsDatabaseKey.condition | YjsDatabaseKey.content | YjsDatabaseKey.filter_type): string;
} }
export interface YDatabaseCalculation extends Y.Map<unknown> { export interface YDatabaseCalculation extends Y.Map<unknown> {
get(key: YjsDatabaseKey.field_id): FieldId; get (key: YjsDatabaseKey.field_id): FieldId;
get(key: YjsDatabaseKey.id | YjsDatabaseKey.type | YjsDatabaseKey.calculation_value): string; get (key: YjsDatabaseKey.id | YjsDatabaseKey.type | YjsDatabaseKey.calculation_value): string;
} }
export interface YDatabaseFieldSettings extends Y.Map<unknown> { export interface YDatabaseFieldSettings extends Y.Map<unknown> {
get(key: FieldId): YDatabaseFieldSetting; get (key: FieldId): YDatabaseFieldSetting;
} }
export interface YDatabaseFieldSetting extends Y.Map<unknown> { export interface YDatabaseFieldSetting extends Y.Map<unknown> {
get(key: YjsDatabaseKey.visibility): string; get (key: YjsDatabaseKey.visibility): string;
get(key: YjsDatabaseKey.wrap): boolean; get (key: YjsDatabaseKey.wrap): boolean;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.width): string; get (key: YjsDatabaseKey.width): string;
} }
export interface YDatabaseMetas extends Y.Map<unknown> { export interface YDatabaseMetas extends Y.Map<unknown> {
get(key: YjsDatabaseKey.iid): string; get (key: YjsDatabaseKey.iid): string;
} }
export interface YDatabaseFields extends Y.Map<YDatabaseField> { export interface YDatabaseFields extends Y.Map<YDatabaseField> {
get(key: FieldId): YDatabaseField; get (key: FieldId): YDatabaseField;
} }
export interface YDatabaseField extends Y.Map<unknown> { export interface YDatabaseField extends Y.Map<unknown> {
get(key: YjsDatabaseKey.name): string; get (key: YjsDatabaseKey.name): string;
get(key: YjsDatabaseKey.id): FieldId; get (key: YjsDatabaseKey.id): FieldId;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.type): string; get (key: YjsDatabaseKey.type): string;
get(key: YjsDatabaseKey.type_option): YDatabaseFieldTypeOption; get (key: YjsDatabaseKey.type_option): YDatabaseFieldTypeOption;
get(key: YjsDatabaseKey.is_primary): boolean; get (key: YjsDatabaseKey.is_primary): boolean;
get(key: YjsDatabaseKey.last_modified): LastModified; get (key: YjsDatabaseKey.last_modified): LastModified;
} }
export interface YDatabaseFieldTypeOption extends Y.Map<unknown> { export interface YDatabaseFieldTypeOption extends Y.Map<unknown> {
// key is the field type // key is the field type
get(key: string): YMapFieldTypeOption; get (key: string): YMapFieldTypeOption;
} }
export interface YMapFieldTypeOption extends Y.Map<unknown> { export interface YMapFieldTypeOption extends Y.Map<unknown> {
get(key: YjsDatabaseKey.content): string; get (key: YjsDatabaseKey.content): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.data): string; get (key: YjsDatabaseKey.data): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.time_format): string; get (key: YjsDatabaseKey.time_format): string;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.date_format): string; get (key: YjsDatabaseKey.date_format): string;
get(key: YjsDatabaseKey.database_id): DatabaseId; get (key: YjsDatabaseKey.database_id): DatabaseId;
// eslint-disable-next-line @typescript-eslint/unified-signatures // eslint-disable-next-line @typescript-eslint/unified-signatures
get(key: YjsDatabaseKey.format): string; get (key: YjsDatabaseKey.format): string;
} }
export enum CollabType { export enum CollabType {

View File

@ -0,0 +1,15 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<g clip-path="url(#clip0_51_33)">
<path d="M7.99998 1.16281V6.63256M7.99998 6.63256L10.0511 4.58138M7.99998 6.63256L5.94885 4.58138"
stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1.16284 8.68369H3.32359C3.94247 8.68369 4.2519 8.68369 4.5239 8.80881C4.7959 8.93387 4.99728 9.16887 5.40003 9.63869L5.81397 10.1217C6.21672 10.5915 6.41815 10.8265 6.69015 10.9516C6.96215 11.0767 7.27159 11.0767 7.8904 11.0767H8.10965C8.72847 11.0767 9.0379 11.0767 9.3099 10.9516C9.58197 10.8265 9.78328 10.5915 10.1861 10.1217L10.6 9.63869C11.0028 9.16887 11.2042 8.93387 11.4762 8.80881C11.7482 8.68369 12.0576 8.68369 12.6765 8.68369H14.8372"
stroke="currentColor" stroke-linecap="round"/>
<path d="M11.4186 1.24956C12.5297 1.35888 13.2777 1.60588 13.8359 2.16413C14.8372 3.16544 14.8372 4.77694 14.8372 8.00006C14.8372 11.2231 14.8372 12.8347 13.8359 13.8359C12.8347 14.8372 11.2231 14.8372 8.00003 14.8372C4.77697 14.8372 3.1654 14.8372 2.16415 13.8359C1.16284 12.8347 1.16284 11.2231 1.16284 8.00006C1.16284 4.77694 1.16284 3.16544 2.16415 2.16413C2.72234 1.60588 3.47028 1.35888 4.58147 1.24956"
stroke="currentColor" stroke-linecap="round"/>
</g>
<defs>
<clipPath id="clip0_51_33">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,15 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_54_39)">
<path d="M9.35439 14.2642H6.64556V15.28H9.35439V14.2642ZM1.73577 9.35441V6.64558H0.719971V9.35441H1.73577ZM14.2641 9.05844V9.35441H15.2799V9.05844H14.2641ZM9.95789 2.99623L12.6388 5.40903L13.3183 4.65398L10.6375 2.24118L9.95789 2.99623ZM15.2799 9.05844C15.2799 7.915 15.2901 7.19109 15.0016 6.54332L14.0738 6.95659C14.2539 7.36108 14.2641 7.8253 14.2641 9.05844H15.2799ZM12.6388 5.40903C13.5554 6.23392 13.8936 6.55211 14.0738 6.95659L15.0016 6.54332C14.7132 5.89554 14.1682 5.41888 13.3183 4.65398L12.6388 5.40903ZM6.66574 1.73585C7.73694 1.73585 8.14118 1.74372 8.5014 1.88195L8.86534 0.933568C8.28851 0.712197 7.65999 0.720059 6.66574 0.720059V1.73585ZM10.6375 2.24118C9.90199 1.57923 9.44223 1.15488 8.86534 0.933568L8.5014 1.88195C8.86181 2.02024 9.16576 2.28334 9.95789 2.99623L10.6375 2.24118ZM6.64556 14.2642C5.35422 14.2642 4.43686 14.2631 3.74086 14.1695C3.05954 14.0779 2.667 13.9061 2.38044 13.6195L1.6621 14.3379C2.16891 14.8446 2.81155 15.0695 3.60554 15.1763C4.38492 15.2811 5.38295 15.28 6.64556 15.28V14.2642ZM0.719971 9.35441C0.719971 10.617 0.718919 11.615 0.823662 12.3944C0.930447 13.1884 1.15529 13.8311 1.6621 14.3379L2.38044 13.6195C2.09382 13.333 1.92204 12.9404 1.83042 12.2591C1.73688 11.5631 1.73577 10.6457 1.73577 9.35441H0.719971ZM9.35439 15.28C10.6169 15.28 11.615 15.2811 12.3943 15.1763C13.1883 15.0695 13.831 14.8446 14.3378 14.3379L13.6195 13.6195C13.3329 13.9061 12.9403 14.0779 12.259 14.1695C11.5631 14.2631 10.6457 14.2642 9.35439 14.2642V15.28ZM14.2641 9.35441C14.2641 10.6457 14.2631 11.5631 14.1695 12.2591C14.0779 12.9404 13.9061 13.333 13.6195 13.6195L14.3378 14.3379C14.8445 13.8311 15.0694 13.1884 15.1762 12.3944C15.281 11.615 15.2799 10.617 15.2799 9.35441H14.2641ZM1.73577 6.64558C1.73577 5.35431 1.73688 4.43688 1.83042 3.74095C1.92204 3.05963 2.09382 2.66709 2.38044 2.38047L1.6621 1.66219C1.15535 2.169 0.930447 2.81164 0.823662 3.60563C0.718919 4.38494 0.719971 5.38297 0.719971 6.64558H1.73577ZM6.66574 0.720059C5.39632 0.720059 4.3934 0.718944 3.61086 0.823687C2.81409 0.930349 2.16929 1.155 1.6621 1.66219L2.38044 2.38047C2.66663 2.09428 3.06034 1.92225 3.74563 1.83051C4.44509 1.73691 5.36772 1.73585 6.66574 1.73585V0.720059Z"
fill="currentColor"/>
<path d="M8.67712 1.56655V3.25958C8.67712 4.85574 8.67712 5.65387 9.17298 6.14973C9.6689 6.64559 10.467 6.64559 12.0632 6.64559H14.772"
stroke="currentColor"/>
<path d="M5.62968 12.4019V9.01585M5.62968 9.01585L4.27527 10.2856M5.62968 9.01585L6.9841 10.2856"
stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_54_39">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -1,18 +1,41 @@
import { IconButton, Tooltip } from '@mui/material'; import { Divider, IconButton, Tooltip } from '@mui/material';
import React from 'react'; import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { ReactComponent as CopyIcon } from '@/assets/copy.svg'; import { ReactComponent as CopyIcon } from '@/assets/copy.svg';
import { ReactComponent as DownloadIcon } from '@/assets/download.svg';
function RightTopActions({ onCopy }: { onCopy: () => void }) { export interface RightTopActionsProps {
onCopy: () => void;
onDownload?: () => void;
}
function RightTopActions ({ onCopy, onDownload }: RightTopActionsProps) {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className={'flex w-fit flex-grow transform items-center justify-end gap-2 rounded bg-bg-body shadow-lg'}> <div className={'flex w-fit flex-grow transform p-1 items-center justify-end gap-1 rounded bg-bg-body shadow-lg'}>
<Tooltip title={t('editor.copy')}> <Tooltip title={t('editor.copy')}>
<IconButton onClick={onCopy}> <IconButton onClick={e => {
e.stopPropagation();
onCopy();
}}
>
<CopyIcon className={'h-6 w-6'} /> <CopyIcon className={'h-6 w-6'} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
{onDownload && <>
<Divider orientation={'vertical'} flexItem />
<Tooltip title={t('button.download')}>
<IconButton className={'p-1'} onClick={e => {
e.stopPropagation();
onDownload?.();
}}
>
<DownloadIcon className={'h-5 w-5'} />
</IconButton>
</Tooltip>
</>}
</div> </div>
); );
} }

View File

@ -1,12 +1,16 @@
import RightTopActions from '@/components/editor/components/block-actions/RightTopActions'; import RightTopActions, { RightTopActionsProps } from '@/components/editor/components/block-actions/RightTopActions';
import React, { useRef } from 'react'; import React, { useRef } from 'react';
function RightTopActionsToolbar({ onCopy, style }: { onCopy: () => void; style?: React.CSSProperties }) { interface RightTopActionsToolbarProps extends RightTopActionsProps {
style?: React.CSSProperties;
}
function RightTopActionsToolbar ({ style, ...props }: RightTopActionsToolbarProps) {
const ref = useRef<HTMLDivElement | null>(null); const ref = useRef<HTMLDivElement | null>(null);
return ( return (
<div ref={ref} style={style} contentEditable={false} className={`block-actions absolute right-2 top-2 z-10`}> <div ref={ref} style={style} contentEditable={false} className={`block-actions absolute right-2 top-2 z-10`}>
<RightTopActions onCopy={onCopy} /> <RightTopActions {...props} />
</div> </div>
); );
} }

View File

@ -0,0 +1,111 @@
import { FieldURLType } from '@/application/collab.type';
import { notify } from '@/components/_shared/notify';
import RightTopActionsToolbar from '@/components/editor/components/block-actions/RightTopActionsToolbar';
import { EditorElementProps, FileNode } from '@/components/editor/editor.type';
import { copyTextToClipboard } from '@/utils/copy';
import { downloadFile } from '@/utils/download';
import { renderDate } from '@/utils/time';
import React, { forwardRef, memo, useCallback, useMemo, useState } from 'react';
import { ReactComponent as FileIcon } from '@/assets/file_upload.svg';
import { useTranslation } from 'react-i18next';
export const FileBlock = memo(
forwardRef<HTMLDivElement, EditorElementProps<FileNode>>(({ node, children, ...attributes }, ref) => {
const { url, name, url_type, uploaded_at } = useMemo(() => node.data || {}, [node.data]);
const className = useMemo(() => {
const classList = ['w-full bg-bg-body py-2'];
if (url) {
classList.push('cursor-pointer');
} else {
classList.push('text-text-caption');
}
if (attributes.className) {
classList.push(attributes.className);
}
return classList.join(' ');
}, [attributes.className, url]);
const [showToolbar, setShowToolbar] = useState(false);
const { t } = useTranslation();
const handleDownload = useCallback(async () => {
try {
if (!url) return;
await downloadFile(url, name);
// eslint-disable-next-line
} catch (e: any) {
notify.error(e.message);
}
}, [url, name]);
const uploadTypePrefix = useMemo(() => {
const time = renderDate(uploaded_at, 'MMM DD, YYYY', false);
if (url_type === FieldURLType.Upload) {
return t('web.fileBlock.uploadedAt', {
time,
});
} else {
return t('web.fileBlock.linkedAt', {
time,
});
}
}, [uploaded_at, url_type, t]);
return (
<div
{...attributes}
className={className}
onMouseEnter={() => {
if (!url) return;
setShowToolbar(true);
}}
onMouseLeave={() => setShowToolbar(false)}
onClick={handleDownload}
>
<div
contentEditable={false}
className={'flex relative w-full gap-4 overflow-hidden px-4 rounded-[8px] border border-line-divider bg-fill-list-active py-4'}
>
<FileIcon className={'w-6 h-6'} />
<div className={'flex-1 flex flex-col gap-2 overflow-hidden text-base font-medium'}>
{url ?
<>
<div className={'w-full truncate'}>{name?.trim() || t('document.title.placeholder')}</div>
<div className={'text-xs'}>
{uploadTypePrefix}
</div>
</> :
<div className={'text-text-caption'}>
{t('web.fileBlock.empty')}
</div>
}
</div>
{showToolbar && url && (
<RightTopActionsToolbar
onDownload={handleDownload}
onCopy={async () => {
if (!url) return;
try {
await copyTextToClipboard(url);
notify.success(t('publish.copy.fileBlock'));
} catch (_) {
// do nothing
}
}}
/>
)}
</div>
<div ref={ref} className={`absolute h-full w-full caret-transparent`}>
{children}
</div>
</div>
);
}));
export default FileBlock;

View File

@ -0,0 +1 @@
export * from './FileBlock';

View File

@ -22,6 +22,7 @@ import { ToggleList } from 'src/components/editor/components/blocks/toggle-list'
import { UnSupportedBlock } from '@/components/editor/components/element/UnSupportedBlock'; import { UnSupportedBlock } from '@/components/editor/components/element/UnSupportedBlock';
import { Formula } from '@/components/editor/components/leaf/formula'; import { Formula } from '@/components/editor/components/leaf/formula';
import { Mention } from '@/components/editor/components/leaf/mention'; import { Mention } from '@/components/editor/components/leaf/mention';
import { FileBlock } from '@/components/editor/components/blocks/file';
import { EditorElementProps, TextNode } from '@/components/editor/editor.type'; import { EditorElementProps, TextNode } from '@/components/editor/editor.type';
import { renderColor } from '@/utils/color'; import { renderColor } from '@/utils/color';
import React, { FC, useMemo } from 'react'; import React, { FC, useMemo } from 'react';
@ -74,6 +75,8 @@ export const Element = ({
return DatabaseBlock; return DatabaseBlock;
case BlockType.LinkPreview: case BlockType.LinkPreview:
return LinkPreview; return LinkPreview;
case BlockType.FileBlock:
return FileBlock;
default: default:
return UnSupportedBlock; return UnSupportedBlock;
} }

View File

@ -6,7 +6,7 @@ import { isFlagEmoji } from '@/utils/emoji';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
function MentionPage({ pageId }: { pageId: string }) { function MentionPage ({ pageId }: { pageId: string }) {
const context = useEditorContext(); const context = useEditorContext();
const { navigateToView, loadViewMeta } = context; const { navigateToView, loadViewMeta } = context;
const [unPublished, setUnPublished] = useState(false); const [unPublished, setUnPublished] = useState(false);

View File

@ -238,7 +238,7 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) {
[data-block-type='heading'] { [data-block-type='heading'] {
.mention-inline .mention-content { .mention-inline .mention-content {
@apply ml-6; @apply ml-7;
} }
.level-1, .level-2 { .level-1, .level-2 {
@ -260,7 +260,7 @@ span[data-slate-placeholder="true"]:not(.inline-block-content) {
} }
.mention-content { .mention-content {
@apply ml-6; @apply ml-7;
} }
} }

View File

@ -17,7 +17,7 @@ import {
BlockId, BlockId,
BlockData, BlockData,
DatabaseNodeData, DatabaseNodeData,
LinkPreviewBlockData, LinkPreviewBlockData, FileBlockData,
} from '@/application/collab.type'; } from '@/application/collab.type';
import { HTMLAttributes } from 'react'; import { HTMLAttributes } from 'react';
import { Element } from 'slate'; import { Element } from 'slate';
@ -98,6 +98,12 @@ export interface LinkPreviewNode extends BlockNode {
data: LinkPreviewBlockData; data: LinkPreviewBlockData;
} }
export interface FileNode extends BlockNode {
type: BlockType.FileBlock;
blockId: string;
data: FileBlockData;
}
export interface MathEquationNode extends BlockNode { export interface MathEquationNode extends BlockNode {
type: BlockType.EquationBlock; type: BlockType.EquationBlock;
blockId: string; blockId: string;

View File

@ -18,7 +18,7 @@ export interface Crumb {
extra?: string | null; extra?: string | null;
} }
function BreadcrumbItem({ crumb, disableClick = false }: { crumb: Crumb; disableClick?: boolean }) { function BreadcrumbItem ({ crumb, disableClick = false }: { crumb: Crumb; disableClick?: boolean }) {
const { viewId, icon, name, layout, extra } = crumb; const { viewId, icon, name, layout, extra } = crumb;
const extraObj: { const extraObj: {
@ -56,11 +56,11 @@ function BreadcrumbItem({ crumb, disableClick = false }: { crumb: Crumb; disable
<span <span
className={'icon h-5 w-5'} className={'icon h-5 w-5'}
style={{ style={{
backgroundColor: extraObj.space_icon_color ? renderColor(extraObj.space_icon_color) : undefined, backgroundColor: extraObj.space_icon_color ? renderColor(extraObj.space_icon_color) : 'rgb(163, 74, 253)',
borderRadius: '8px', borderRadius: '8px',
}} }}
> >
<SpaceIcon value={extraObj.space_icon || ''} /> <SpaceIcon value={extraObj.space_icon || ''} char={extraObj.space_icon ? undefined : name.slice(0, 1)} />
</span> </span>
) : ( ) : (
<span className={`${isFlag ? 'icon' : ''} flex h-5 w-5 items-center justify-center`}> <span className={`${isFlag ? 'icon' : ''} flex h-5 w-5 items-center justify-center`}>

View File

@ -55,17 +55,17 @@ export const getIconComponent = (icon: string) => {
} }
}; };
function SpaceIcon({ value }: { value: string }) { function SpaceIcon ({ value, char }: { value: string, char?: string }) {
const IconComponent = getIconComponent(value); const IconComponent = getIconComponent(value);
const [iconEncodeContent, setIconEncodeContent] = useState<string | null>(null); const [iconEncodeContent, setIconEncodeContent] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
if (value && !IconComponent) { if (!char && value && !IconComponent) {
void getIconSvgEncodedContent(value, 'white').then((res) => { void getIconSvgEncodedContent(value, 'white').then((res) => {
setIconEncodeContent(res); setIconEncodeContent(res);
}); });
} }
}, [IconComponent, value]); }, [IconComponent, value, char]);
const customIcon = useMemo(() => { const customIcon = useMemo(() => {
if (!iconEncodeContent) { if (!iconEncodeContent) {
@ -78,6 +78,14 @@ function SpaceIcon({ value }: { value: string }) {
return <img src={iconEncodeContent} className={'h-full w-full p-1 text-white'} alt={value} />; return <img src={iconEncodeContent} className={'h-full w-full p-1 text-white'} alt={value} />;
}, [iconEncodeContent, value]); }, [iconEncodeContent, value]);
if (char) {
return (
<span className={'text-content-on-fill font-medium h-full w-full flex items-center justify-center'}>
{char}
</span>
);
}
if (!IconComponent) { if (!IconComponent) {
return customIcon; return customIcon;
} }

View File

@ -14,7 +14,7 @@ export interface SpaceListProps {
loading?: boolean; loading?: boolean;
} }
function SpaceList({ loading, spaceList, value, onChange }: SpaceListProps) { function SpaceList ({ loading, spaceList, value, onChange }: SpaceListProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const getExtraObj = useCallback((extra: string) => { const getExtraObj = useCallback((extra: string) => {
@ -40,11 +40,13 @@ function SpaceList({ loading, spaceList, value, onChange }: SpaceListProps) {
<span <span
className={'icon h-5 w-5'} className={'icon h-5 w-5'}
style={{ style={{
backgroundColor: extraObj.space_icon_color ? renderColor(extraObj.space_icon_color) : undefined, backgroundColor: extraObj.space_icon_color ? renderColor(extraObj.space_icon_color) : 'rgb(163, 74, 253)',
borderRadius: '8px', borderRadius: '8px',
}} }}
> >
<SpaceIcon value={extraObj.space_icon || ''} /> <SpaceIcon value={extraObj.space_icon || ''}
char={extraObj.space_icon ? undefined : space.name.slice(0, 1)}
/>
</span> </span>
<div className={'flex flex-1 items-center gap-2 truncate'}> <div className={'flex flex-1 items-center gap-2 truncate'}>
{space.name} {space.name}
@ -53,7 +55,7 @@ function SpaceList({ loading, spaceList, value, onChange }: SpaceListProps) {
</div> </div>
); );
}, },
[getExtraObj] [getExtraObj],
); );
return ( return (

View File

@ -0,0 +1,27 @@
export async function downloadFile (url: string, filename?: string): Promise<void> {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Download failed, the download status is: ${response.status}`);
}
const blob = await response.blob();
const anchor = document.createElement('a');
const blobUrl = window.URL.createObjectURL(blob);
anchor.href = blobUrl;
anchor.download = filename || url.split('/').pop() || 'download';
document.body.appendChild(anchor);
anchor.click();
document.body.removeChild(anchor);
window.URL.revokeObjectURL(blobUrl);
} catch (error) {
return Promise.reject(error);
}
}

View File

@ -1,6 +1,6 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
export function renderDate(date: string, format: string, isUnix?: boolean): string { export function renderDate (date: string | number, format: string, isUnix?: boolean): string {
if (isUnix) return dayjs.unix(Number(date)).format(format); if (isUnix) return dayjs.unix(Number(date)).format(format);
return dayjs(date).format(format); return dayjs(date).format(format);
} }

View File

@ -375,7 +375,8 @@
"close": "Close", "close": "Close",
"next": "Next", "next": "Next",
"previous": "Previous", "previous": "Previous",
"submit": "Submit" "submit": "Submit",
"download": "Download"
}, },
"label": { "label": {
"welcome": "Welcome!", "welcome": "Welcome!",
@ -2327,7 +2328,8 @@
"copy": { "copy": {
"codeBlock": "The content of code block has been copied to the clipboard", "codeBlock": "The content of code block has been copied to the clipboard",
"imageBlock": "The image link has been copied to the clipboard", "imageBlock": "The image link has been copied to the clipboard",
"mathBlock": "The math equation has been copied to the clipboard" "mathBlock": "The math equation has been copied to the clipboard",
"fileBlock": "The file link has been copied to the clipboard"
}, },
"containsPublishedPage": "This page contains one or more published pages. If you continue, they will be unpublished. Do you want to proceed with deletion?", "containsPublishedPage": "This page contains one or more published pages. If you continue, they will be unpublished. Do you want to proceed with deletion?",
"publishSuccessfully": "Published successfully", "publishSuccessfully": "Published successfully",
@ -2375,7 +2377,12 @@
"termOfUse": "Terms", "termOfUse": "Terms",
"privacyPolicy": "Privacy Policy", "privacyPolicy": "Privacy Policy",
"signInError": "Sign in error", "signInError": "Sign in error",
"login": "Sign up or log in" "login": "Sign up or log in",
"fileBlock": {
"uploadedAt": "Uploaded on {time}",
"linkedAt": "Link added on {time}",
"empty": "Upload or embed a file"
}
}, },
"globalComment": { "globalComment": {
"comments": "Comments", "comments": "Comments",