Skip to content
Merged
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
20 changes: 16 additions & 4 deletions apps/web/src/root.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
// A workaround to make the globals.css file work in the web app
import { lazy, Suspense } from 'react';
import { useRegisterSW } from 'virtual:pwa-register/react';

import '../../../packages/ui/src/styles/globals.css';

import { useRegisterSW } from 'virtual:pwa-register/react';
const App = lazy(() =>
import('@colanode/ui').then((module) => ({ default: module.App }))
);

import { App } from '@colanode/ui';
const RootLoading = () => (
<div className="flex min-h-screen w-full items-center justify-center">
<p className="text-sm text-muted-foreground">Loading…</p>
</div>
);

export const Root = () => {
useRegisterSW({
Expand All @@ -12,5 +20,9 @@ export const Root = () => {
},
});

return <App type="web" />;
return (
<Suspense fallback={<RootLoading />}>
<App type="web" />
</Suspense>
);
};
30 changes: 15 additions & 15 deletions docs/file-meta-types.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# File Meta Types — Design

> Status: **implemented (Phase 1 MVP)**
> Status: **implemented (Phase 1 MVP + Phase 2 polish)**
> Branch target: `main`
> Related: [page-meta-types.md](./page-meta-types.md), [asset-library.md](./asset-library.md), [roadmap.md](./roadmap.md) §4

Expand Down Expand Up @@ -302,7 +302,7 @@ export type MetaTypeInstanceKind = 'page' | 'file';

**Labels:** `isFileMetaType` branches — "Add file", "No files", "File ID" (parallel to page strings).

**Create:** `MetaTypeFileCreateButton` — upload dialog with meta type fixed to current schema; pick parent folder/space.
**Create:** `MetaTypeFileUploadButton` / `MetaTypeFileUploadDialog` — upload with space + optional folder picker; meta type fixed to current schema.

### File detail

Expand Down Expand Up @@ -405,25 +405,25 @@ Meta type schema edits: same as page meta types (admin on schema DB).

**Core**

- [ ] `instanceKind` on meta type databases (default `page`)
- [ ] `file.metaTypeId` + `fields` in core schema + immutability
- [ ] `ensureFileMetaTypes` + default **File** schema + backfill
- [ ] `resolveDatabaseCollectionSource` + SQL for `file`
- [ ] `file.byMetaType` query
- [ ] `file.create` accepts `metaTypeId`
- [ ] `database.query` maps file rows
- [x] `instanceKind` on meta type databases (default `page`)
- [x] `file.metaTypeId` + `fields` in core schema + immutability
- [x] `ensureFileMetaTypes` + default **File** schema + backfill
- [x] `resolveDatabaseCollectionSource` + SQL for `file`
- [x] `file.byMetaType` query
- [x] `file.create` accepts `metaTypeId`
- [x] `database.query` maps file rows

**UI**

- [ ] Meta type create: pick Page / File
- [ ] `FileMetadata` + `FileFieldsProvider`
- [ ] Meta type views: gallery/table/list/**calendar**/board for files
- [ ] Upload with meta type picker (folder direct path)
- [x] Meta type create: pick Page / File
- [x] `FileMetadata` + `FileFieldsProvider`
- [x] Meta type views: gallery/table/list/**calendar**/board for files
- [x] Upload with meta type picker (folder direct path)

**Docs**

- [ ] This doc → status **implemented** when done
- [ ] Update `roadmap.md`, `AGENTS.md`, skill reference
- [x] This doc → status **implemented**
- [x] Update `roadmap.md`, `AGENTS.md`, skill reference

### Phase 2 — Automation & polish

Expand Down
16 changes: 12 additions & 4 deletions docs/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@
- 本地 sync:首次 import 后再 `ensurePageMetaTypes`,重复默认 Page 去重
- 测试:`packages/client/test/page-meta-types.test.ts`、`field-defaults.test.ts`、`cloud-sync.test.ts` 等

### File Meta Types(MVP + Phase 2)

- Schema:`database.instanceKind: 'page' | 'file'`;`file.metaTypeId` + `fields`
- 聚合:meta type 数据库视图按 `metaTypeId` 拉取各 Space 下的 File(排除 Asset Library 子文件)
- 全视图(Table / Gallery / List / Board / Calendar)、board 拖拽、Relation 搜索/展示/筛选
- Scheduled Tasks:`meta_type_files_created`、`meta_type_file_field_changed`
- 详见 [file-meta-types.md](./file-meta-types.md)

### 其他已落地基础

- Workspace 级 Tags **未合入** Colapp 分支(见 [tags-implementation-guide.md](./tags-implementation-guide.md);当前「Tags」多为 Database `multi_select` 字段)
Expand All @@ -48,7 +56,7 @@
| 1 | **MCP + Skills**(给 LLM 用) | 弱(偏研发工具) | 中 | 提升 AI 协作开发效率 | P1(若持续用 AI 改本仓库)— **薄 MVP 已落地** |
| 2 | **消息通知 + 定时任务** | **强**(`JobService` / `job_schedules`) | 中 | 高(桌面自动化) | P1(产品向) |
| 3 | **文档导入导出**(Markdown / PDF) | **较强**(Tiptap Markdown、Page CRDT) | 中→大 | 高(Obsidian 迁移) | P1–P2 |
| 4 | **目录 / 文件夹增强 + 资源 Meta Types** | 中(`folder` + 全局 Assets 已落地;Meta Types 现仅 Page) | **大** | 高,后续可增强批量标签、跨 Folder 搜索 | P2–P3 |
| 4 | **目录 / 文件夹增强 + 资源 Meta Types** | 中(`folder` + 全局 Assets 已落地;**File Meta Types 已落地**) | **大** | 高,后续可增强批量标签、跨 Folder 搜索 | P2–P3 |
| 5 | **新类型:白板 / 画布** | 弱(新节点 + 新 CRDT) | **很大** | 高,周期长 | P3+ |

### 1. MCP + Skills
Expand Down Expand Up @@ -119,19 +127,19 @@
**现状:**

- `folder` 节点:`name` + 父级关系;Asset Library 中 Folder 作**过滤视图**(非独立资产库)
- Meta Types **当前只绑定 Page**(`page.metaTypeId`
- Meta Types 已泛化到 **Page** 与 **File**(`page.metaTypeId` / `file.metaTypeId`);Asset Library 仍为独立静态资源子系统
- 已有 `file` 节点、`resource` 字段类型,但与「目录同步」不是同一概念
- **Markdown 目录导入(薄 MVP)**:Space 菜单 → Import Markdown folder;保留子目录为 Folder 树,每个 `.md` 导入为 Page(frontmatter → fields);桌面用目录选择器,Web 用文件夹选择

**架构选项:**

| 方案 | 说明 |
|------|------|
| A | 将 Meta Types 泛化到 `file` / 新 `resource` 节点 |
| A | 将 Meta Types 泛化到 `file` / 新 `resource` 节点 | **File Meta Types 已落地**(见 [file-meta-types.md](./file-meta-types.md)) |
| B | 目录导入仅生成 Page 索引 + 外链文件(轻量) |
| C | 完整 DAM(元数据 + 预览 + 同步)——体量最大 |

**建议:** 在方向 3 完成「单文件 md 导入 + frontmatter」后,再决定是否泛化 Meta Types,避免与 Page Meta Types 双轨逻辑
**建议:** File Meta Types MVP 已合入;后续可增强上传选位置、Gallery 缩略图优化、跨 Folder 搜索等

---

Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/lib/meta-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ export const META_TYPES_SPACE_MARKER = '__colanode_meta_types__';
export const DEFAULT_PAGE_META_TYPE_NAME = 'Page';
export const DEFAULT_FILE_META_TYPE_NAME = 'File';

export const isMetaTypesSpace = (node: {
type?: string;
description?: string | null;
}): boolean =>
node.type === 'space' && node.description === META_TYPES_SPACE_MARKER;

export const metaTypeInstanceKindSchema = z.enum(['page', 'file']);
export type MetaTypeInstanceKind = z.infer<typeof metaTypeInstanceKindSchema>;

Expand Down
32 changes: 24 additions & 8 deletions packages/ui/src/components/app/app-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { lazy, Suspense } from 'react';
import { match } from 'ts-pattern';

import { AppType } from '@colanode/client/types';
import { LayoutDesktop } from '@colanode/ui/components/layouts/layout-desktop';
import { LayoutMobile } from '@colanode/ui/components/layouts/layout-mobile';
import { LayoutWeb } from '@colanode/ui/components/layouts/layout-web';

const LayoutDesktop = lazy(() =>
import('@colanode/ui/components/layouts/layout-desktop').then((module) => ({
default: module.LayoutDesktop,
}))
);
const LayoutMobile = lazy(() =>
import('@colanode/ui/components/layouts/layout-mobile').then((module) => ({
default: module.LayoutMobile,
}))
);
const LayoutWeb = lazy(() =>
import('@colanode/ui/components/layouts/layout-web').then((module) => ({
default: module.LayoutWeb,
}))
);

interface AppLayoutProps {
type: AppType;
Expand All @@ -12,11 +26,13 @@ interface AppLayoutProps {
export const AppLayout = ({ type }: AppLayoutProps) => {
return (
<div className="h-[100dvh] w-[100dvw] bg-background text-foreground">
{match(type)
.with('desktop', () => <LayoutDesktop />)
.with('mobile', () => <LayoutMobile />)
.with('web', () => <LayoutWeb />)
.exhaustive()}
<Suspense fallback={null}>
{match(type)
.with('desktop', () => <LayoutDesktop />)
.with('mobile', () => <LayoutMobile />)
.with('web', () => <LayoutWeb />)
.exhaustive()}
</Suspense>
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { extractNodeRole } from '@colanode/core';
import { BoardViewCollectionCard } from '@colanode/ui/components/databases/boards/board-view-collection-card';
import { BoardViewRecordCreateCard } from '@colanode/ui/components/databases/boards/board-view-record-create-card';
import { FileFieldsProvider } from '@colanode/ui/components/files/file-fields-provider';
import { MetaTypeFileUploadButton } from '@colanode/ui/components/files/meta-type-file-upload-button';
import { MetaTypePageCreateButton } from '@colanode/ui/components/pages/meta-type-page-create-button';
import { MetaTypeCreateButton } from '@colanode/ui/components/pages/meta-type-create-button';
import { PageFieldsProvider } from '@colanode/ui/components/pages/page-fields-provider';
import { RecordProvider } from '@colanode/ui/components/records/record-provider';
import { useBoardView } from '@colanode/ui/contexts/board-view';
Expand Down Expand Up @@ -53,12 +52,7 @@ export const BoardViewColumnRecords = () => {
{database.canCreateRecord && (
<BoardViewRecordCreateCard filters={filters} />
)}
<MetaTypePageCreateButton
label="Add page"
className="animate-fade-in flex min-h-12 cursor-pointer flex-row items-center justify-center gap-1 rounded-md border border-dashed text-muted-foreground hover:bg-accent"
/>
<MetaTypeFileUploadButton
label="Add file"
<MetaTypeCreateButton
className="animate-fade-in flex min-h-12 cursor-pointer flex-row items-center justify-center gap-1 rounded-md border border-dashed text-muted-foreground hover:bg-accent"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '@colanode/core';
import { CalendarViewDay } from '@colanode/ui/components/databases/calendars/calendar-view-day';
import { MetaTypePageCreateDialog } from '@colanode/ui/components/pages/meta-type-page-create-dialog';
import { MetaTypeFileUploadDialog } from '@colanode/ui/components/files/meta-type-file-upload-dialog';
import { buttonVariants } from '@colanode/ui/components/ui/button';
import { useDatabase } from '@colanode/ui/contexts/database';
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
Expand All @@ -29,6 +30,8 @@ const toUTCDate = (dateParam: Date | string): Date => {
return new Date(Date.UTC(year, month, day, 0, 0, 0, 0));
};

const CALENDAR_QUERY_LIMIT = 200;

interface CalendarViewGridProps {
field: FieldAttributes;
}
Expand All @@ -43,6 +46,10 @@ export const CalendarViewGrid = ({ field }: CalendarViewGridProps) => {

const [month, setMonth] = useState(new Date());
const [pageCreateOpen, setPageCreateOpen] = useState(false);
const [fileUploadOpen, setFileUploadOpen] = useState(false);
const [fileUploadFields, setFileUploadFields] = useState<
Record<string, FieldValue>
>({});
const [pageCreateFields, setPageCreateFields] = useState<
Record<string, FieldValue>
>({});
Expand All @@ -69,11 +76,18 @@ export const CalendarViewGrid = ({ field }: CalendarViewGridProps) => {
[view.filters, field.id, first, last]
);

const { data } = useCollectionQuery(filters, view.sorts, 200);
const { data } = useCollectionQuery(filters, view.sorts, CALENDAR_QUERY_LIMIT);
const rows = data;
const isQueryCapped = rows.length >= CALENDAR_QUERY_LIMIT;

return (
<>
{isQueryCapped && (
<p className="px-3 pb-2 text-xs text-muted-foreground">
Showing the first {CALENDAR_QUERY_LIMIT.toLocaleString()} items in this
month. Narrow filters to see more.
</p>
)}
<DayPicker
showOutsideDays
className="p-3"
Expand Down Expand Up @@ -206,6 +220,12 @@ export const CalendarViewGrid = ({ field }: CalendarViewGridProps) => {
(field.type === 'date' ||
(field.type === 'created_at' &&
isSameDay(props.day.date, new Date())));
const canCreateFile =
isMetaType &&
database.canCreateFile &&
(field.type === 'date' ||
(field.type === 'created_at' &&
isSameDay(props.day.date, new Date())));

const onCreate = canCreateRecord
? database.canCreateRecord
Expand All @@ -222,7 +242,18 @@ export const CalendarViewGrid = ({ field }: CalendarViewGridProps) => {
);
setPageCreateOpen(true);
}
: undefined;
: canCreateFile
? () => {
setFileUploadFields(
generateFieldValuesFromFilters(
database.fields,
[filter],
workspace.userId
)
);
setFileUploadOpen(true);
}
: undefined;

return (
<CalendarViewDay
Expand All @@ -241,6 +272,12 @@ export const CalendarViewGrid = ({ field }: CalendarViewGridProps) => {
onOpenChange={setPageCreateOpen}
initialFields={pageCreateFields}
/>
<MetaTypeFileUploadDialog
metaTypeId={database.id}
open={fileUploadOpen}
onOpenChange={setFileUploadOpen}
initialFields={fileUploadFields}
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { useWorkspace } from '@colanode/ui/contexts/workspace';
import { useCollectionQuery } from '@colanode/ui/hooks/use-collection-query';
import { useLiveQuery } from '@colanode/ui/hooks/use-live-query';
import { filterCollectionRows } from '@colanode/ui/lib/collection-view-query';
import { getMetaTypeEntityNames } from '@colanode/ui/lib/meta-types';

interface CalendarViewNoValueCountProps {
field: FieldAttributes;
Expand Down Expand Up @@ -78,6 +79,7 @@ export const CalendarViewNoValueCount = ({
const noValueCount = isMetaType
? metaTypeNoValueCount
: (noValueCountQuery.data?.noValueCount ?? 0);
const entityNames = getMetaTypeEntityNames(database);

if (noValueCount === 0) {
return null;
Expand All @@ -99,7 +101,9 @@ export const CalendarViewNoValueCount = ({
<DialogHeader>
<DialogTitle>{view.name}</DialogTitle>
<DialogDescription>
Record with no {field.name} value ({noValueCount.toLocaleString()})
{entityNames.singular.charAt(0).toUpperCase() +
entityNames.singular.slice(1)}{' '}
with no {field.name} value ({noValueCount.toLocaleString()})
</DialogDescription>
</DialogHeader>
<ScrollArea className="h-96">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { DatabaseViewFilterAttributes, FieldAttributes } from '@colanode/core';
import { Avatar } from '@colanode/ui/components/avatars/avatar';
import { Link } from '@colanode/ui/components/ui/link';
import { useDatabase } from '@colanode/ui/contexts/database';
import { useDatabaseView } from '@colanode/ui/contexts/database-view';
import { useCollectionQuery } from '@colanode/ui/hooks/use-collection-query';
import { getMetaTypeEntityNames } from '@colanode/ui/lib/meta-types';

interface CalendarViewNoValueListProps {
filters: DatabaseViewFilterAttributes[];
Expand All @@ -13,7 +15,9 @@ export const CalendarViewNoValueList = ({
filters,
field,
}: CalendarViewNoValueListProps) => {
const database = useDatabase();
const view = useDatabaseView();
const entityNames = getMetaTypeEntityNames(database);

const { data } = useCollectionQuery(filters, view.sorts);
const rows = data;
Expand All @@ -22,7 +26,7 @@ export const CalendarViewNoValueList = ({
<div className="flex flex-col gap-2 overflow-y-auto">
{rows.length === 0 && (
<div className="text-center text-sm text-muted-foreground">
No records with no {field.name} value
No {entityNames.plural} with no {field.name} value
</div>
)}
{rows.map((row) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ViewSearchBar } from '@colanode/ui/components/databases/search/view-sea
import { ViewSortButton } from '@colanode/ui/components/databases/search/view-sort-button';
import { ViewFullscreenButton } from '@colanode/ui/components/databases/view-fullscreen-button';
import { ViewTabs } from '@colanode/ui/components/databases/view-tabs';
import { MetaTypePageCreateButton } from '@colanode/ui/components/pages/meta-type-page-create-button';
import { MetaTypeCreateButton } from '@colanode/ui/components/pages/meta-type-create-button';
import { useDatabase } from '@colanode/ui/contexts/database';
import { useDatabaseView } from '@colanode/ui/contexts/database-view';

Expand Down Expand Up @@ -40,8 +40,7 @@ export const CalendarView = () => {
) : (
<CalendarViewNoGroup />
)}
<MetaTypePageCreateButton
label="Add page"
<MetaTypeCreateButton
className="mt-4 flex min-h-10 w-full max-w-xs cursor-pointer flex-row items-center justify-center gap-1 rounded-md border border-dashed text-muted-foreground hover:bg-accent"
/>
</div>
Expand Down
Loading
Loading