mirror of
https://github.com/penxio/penx.git
synced 2026-05-12 03:03:12 -04:00
feat: can sync to remote
This commit is contained in:
@@ -11,8 +11,6 @@ const PageEditor = () => {
|
||||
|
||||
const { data } = useSession()
|
||||
|
||||
console.log('sesion========data:', data)
|
||||
|
||||
return (
|
||||
<WalletConnectProvider>
|
||||
<EditorApp />
|
||||
|
||||
@@ -11,7 +11,8 @@ export const Database = ({
|
||||
const { databaseId } = element
|
||||
|
||||
return (
|
||||
<Box flex-1 contentEditable={false} {...attributes}>
|
||||
<Box flex-1 {...attributes}>
|
||||
{children}
|
||||
<TableView databaseId={databaseId}>{children}</TableView>
|
||||
</Box>
|
||||
)
|
||||
|
||||
@@ -18,6 +18,8 @@ export const Tag = ({
|
||||
async function clickTag() {
|
||||
const database = await db.getDatabaseByName(element.name)
|
||||
if (database) {
|
||||
console.log('=====database:', database)
|
||||
|
||||
store.selectNode(database)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +33,7 @@ export const Tag = ({
|
||||
rounded
|
||||
overflowHidden
|
||||
ringBrand500={selected}
|
||||
contentEditable={false}
|
||||
>
|
||||
{children}
|
||||
<Box
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { z } from 'zod'
|
||||
import { INode } from '@penx/model-types'
|
||||
import { syncNodes, syncNodesInput } from '../service/syncNodes'
|
||||
import { createTRPCRouter, publicProcedure } from '../trpc'
|
||||
|
||||
export const nodeRouter = createTRPCRouter({
|
||||
@@ -9,4 +11,8 @@ export const nodeRouter = createTRPCRouter({
|
||||
where: { spaceId: input.spaceId },
|
||||
})
|
||||
}),
|
||||
|
||||
sync: publicProcedure.input(syncNodesInput).mutation(({ ctx, input }) => {
|
||||
return syncNodes(input)
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -4,14 +4,6 @@ import { prisma } from '@penx/db'
|
||||
import { INode, ISpace } from '@penx/model-types'
|
||||
import { RoleType } from '../constants'
|
||||
|
||||
const EDITOR_CONTENT = [
|
||||
{
|
||||
type: 'p',
|
||||
id: nanoid(),
|
||||
children: [{ text: 'A page' }],
|
||||
},
|
||||
]
|
||||
|
||||
export const CreateSpaceInput = z.object({
|
||||
userId: z.string().min(1),
|
||||
spaceData: z.string(),
|
||||
@@ -22,8 +14,6 @@ export type CreateUserInput = z.infer<typeof CreateSpaceInput>
|
||||
|
||||
export function createSpace(input: CreateUserInput) {
|
||||
const { userId, spaceData, nodesData } = input
|
||||
console.log('===========userId:', userId)
|
||||
|
||||
const space: ISpace = JSON.parse(spaceData)
|
||||
const nodes: INode[] = JSON.parse(nodesData)
|
||||
|
||||
|
||||
96
packages/api/src/service/syncNodes.ts
Normal file
96
packages/api/src/service/syncNodes.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import { TRPCError } from '@trpc/server'
|
||||
import { z } from 'zod'
|
||||
import { prisma } from '@penx/db'
|
||||
import { INode, ISpace } from '@penx/model-types'
|
||||
|
||||
export const syncNodesInput = z.object({
|
||||
version: z.number(),
|
||||
spaceId: z.string(),
|
||||
added: z.string(),
|
||||
updated: z.string(),
|
||||
deleted: z.string(),
|
||||
})
|
||||
|
||||
export type SyncUserInput = z.infer<typeof syncNodesInput>
|
||||
|
||||
export function syncNodes(input: SyncUserInput) {
|
||||
const added: INode[] = JSON.parse(input.added)
|
||||
const updated: INode[] = JSON.parse(input.updated)
|
||||
const deleted: string[] = JSON.parse(input.deleted)
|
||||
// console.log('added:', added)
|
||||
// console.log('updated:', updated)
|
||||
// console.log('deleted:', deleted)
|
||||
|
||||
return prisma.$transaction(
|
||||
async (tx) => {
|
||||
const space = await tx.space.findUniqueOrThrow({
|
||||
where: { id: input.spaceId },
|
||||
})
|
||||
|
||||
console.log(
|
||||
'input.version:',
|
||||
input.version,
|
||||
'space.version:',
|
||||
space.version,
|
||||
)
|
||||
|
||||
if (input.version < space.version) {
|
||||
throw new TRPCError({
|
||||
code: 'BAD_REQUEST',
|
||||
message: 'Version invalid',
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: need to improve this
|
||||
for (const item of added) {
|
||||
const node = await tx.node.findUnique({ where: { id: item.id } })
|
||||
const { openedAt, createdAt, updatedAt, ...rest } = item
|
||||
if (node) {
|
||||
await tx.node.update({
|
||||
where: { id: item.id },
|
||||
data: {
|
||||
...rest,
|
||||
openedAt: new Date(openedAt),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
await tx.node.create({ data: rest })
|
||||
}
|
||||
}
|
||||
|
||||
for (const item of updated) {
|
||||
const { openedAt, createdAt, updatedAt, ...rest } = item
|
||||
|
||||
try {
|
||||
await tx.node.update({
|
||||
where: { id: item.id },
|
||||
data: {
|
||||
...rest,
|
||||
openedAt: new Date(openedAt),
|
||||
},
|
||||
})
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
for (const id of deleted) {
|
||||
try {
|
||||
await tx.node.delete({
|
||||
where: { id: id },
|
||||
})
|
||||
} catch (error) {}
|
||||
}
|
||||
|
||||
const newVersion = space.version + 1
|
||||
await tx.space.update({
|
||||
where: { id: input.spaceId },
|
||||
data: { version: newVersion },
|
||||
})
|
||||
|
||||
return newVersion
|
||||
},
|
||||
{
|
||||
maxWait: 5000, // default: 2000
|
||||
timeout: 10000, // default: 5000
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -4,16 +4,11 @@ import {
|
||||
Button,
|
||||
MenuItem,
|
||||
Popover,
|
||||
PopoverClose,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from 'uikit'
|
||||
import { useNodeContext } from '@penx/hooks'
|
||||
import { store } from '@penx/store'
|
||||
|
||||
export const MorePopover = () => {
|
||||
const { node } = useNodeContext()
|
||||
|
||||
return (
|
||||
<Popover placement="bottom-end">
|
||||
<PopoverTrigger asChild>
|
||||
@@ -22,18 +17,6 @@ export const MorePopover = () => {
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent w-260 column>
|
||||
<PopoverClose>
|
||||
<MenuItem
|
||||
gap2
|
||||
onClick={async () => {
|
||||
await store.trashNode(store.getNode().id)
|
||||
}}
|
||||
>
|
||||
<Trash2 size={18} />
|
||||
<Box>Delete</Box>
|
||||
</MenuItem>
|
||||
</PopoverClose>
|
||||
|
||||
<MenuItem gap2 onClick={async () => {}}>
|
||||
<StarOff size={18} />
|
||||
<Box>Remove from Favorites</Box>
|
||||
|
||||
@@ -76,6 +76,8 @@ interface LinkedReferencesProps {
|
||||
}
|
||||
|
||||
export function LinkedReferences({ node }: LinkedReferencesProps) {
|
||||
// console.log('=========x:', node)
|
||||
|
||||
const { nodeList } = useNodes()
|
||||
const linkedNodes = nodeList.getLinkedReferences(node)
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import isEqual from 'react-fast-compare'
|
||||
import { useState } from 'react'
|
||||
import { Box } from '@fower/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
import { NodeEditor } from '@penx/editor'
|
||||
import { isAstChange } from '@penx/editor-queries'
|
||||
import { NodeProvider, useNodes } from '@penx/hooks'
|
||||
import { db } from '@penx/local-db'
|
||||
import { Node } from '@penx/model'
|
||||
import { INode } from '@penx/model-types'
|
||||
import { nodeToSlate, slateToNodes } from '@penx/serializer'
|
||||
import { NodeService } from '@penx/service'
|
||||
import { routerAtom } from '@penx/store'
|
||||
import { diffNodes, NodeListService, NodeService } from '@penx/service'
|
||||
import { routerAtom, store } from '@penx/store'
|
||||
import { trpc } from '@penx/trpc-client'
|
||||
import { withBulletPlugin } from '../plugins/withBulletPlugin'
|
||||
import { MobileNav } from './DocNav/MobileNav'
|
||||
import { PCNav } from './DocNav/PCNav'
|
||||
@@ -20,53 +21,50 @@ interface Props {
|
||||
node: Node
|
||||
}
|
||||
|
||||
// TODO: need improve performance
|
||||
function diff(oldNodes: INode[], newNodes: INode[]) {
|
||||
console.log('oldNodes:', oldNodes, 'newNodes:', newNodes)
|
||||
|
||||
const added: INode[] = []
|
||||
const updated: INode[] = []
|
||||
const deleted: INode[] = []
|
||||
|
||||
for (const b of newNodes) {
|
||||
const a = oldNodes.find((n) => n.id === b.id)
|
||||
if (!a) {
|
||||
added.push(b)
|
||||
continue
|
||||
}
|
||||
|
||||
// should custom isEqual
|
||||
if (!isEqual(a, b)) {
|
||||
updated.push(b)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for (const a of oldNodes) {
|
||||
const b = newNodes.find((n) => n.id === a.id)
|
||||
if (!b) {
|
||||
deleted.push(a)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return { added, updated, deleted }
|
||||
}
|
||||
|
||||
export function PanelItem({ node, index }: Props) {
|
||||
const { nodes, nodeList } = useNodes()
|
||||
const { name } = useAtomValue(routerAtom)
|
||||
const nodeService = new NodeService(node, nodes)
|
||||
|
||||
const [saving, setSaving] = useState(false)
|
||||
|
||||
const content = nodeToSlate(node.raw, nodeList.rawNodes)
|
||||
|
||||
const debouncedSaveNodes = useDebouncedCallback(async (value: any[]) => {
|
||||
const oldNodes = nodeList.flattenNode(node).map((node) => node.raw)
|
||||
const newNodes = slateToNodes(node.raw, value, nodeList.rawNodes)
|
||||
const diffed = diff([node.raw, ...oldNodes], newNodes)
|
||||
|
||||
await nodeService.savePage(node.raw, value[0], value[1])
|
||||
|
||||
/**
|
||||
* sync to cloud
|
||||
*/
|
||||
const activeSpace = store.getActiveSpace()
|
||||
if (!activeSpace.isCloud) return
|
||||
|
||||
// TODO: need to improve
|
||||
const newNode = await db.getNode(node.id)
|
||||
const nodes = await db.listNodesBySpaceId(node.spaceId)
|
||||
const nodeListService = new NodeListService(nodes)
|
||||
const newNodes = nodeListService
|
||||
.flattenNode(new Node(newNode))
|
||||
.map((node) => node.raw)
|
||||
|
||||
const diffed = diffNodes([node.raw, ...oldNodes], [newNode, ...newNodes])
|
||||
|
||||
console.log('====diffed:', diffed)
|
||||
|
||||
nodeService.savePage(node.raw, value[0], value[1])
|
||||
}, 500)
|
||||
const newVersion = await trpc.node.sync.mutate({
|
||||
version: activeSpace.version,
|
||||
spaceId: node.spaceId,
|
||||
added: JSON.stringify(diffed.added),
|
||||
updated: JSON.stringify(diffed.updated),
|
||||
deleted: JSON.stringify(diffed.deleted.map((n) => n.id)),
|
||||
})
|
||||
|
||||
console.log('=========newVersion:', newVersion)
|
||||
|
||||
await store.updateSpace(activeSpace.id, { version: newVersion })
|
||||
}, 1000)
|
||||
|
||||
// console.log('====content:', index, content)
|
||||
|
||||
@@ -88,9 +86,12 @@ export function PanelItem({ node, index }: Props) {
|
||||
plugins={[withBulletPlugin]}
|
||||
content={content}
|
||||
node={node}
|
||||
onChange={(value, editor) => {
|
||||
onChange={async (value, editor) => {
|
||||
if (isAstChange(editor)) {
|
||||
debouncedSaveNodes(value)
|
||||
if (saving) return
|
||||
setSaving(true)
|
||||
await debouncedSaveNodes(value)
|
||||
setSaving(false)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { Box } from '@fower/react'
|
||||
import { PopoverClose, Tag } from 'uikit'
|
||||
import { Space } from '@penx/model'
|
||||
import { ISpace } from '@penx/model-types'
|
||||
import { store } from '@penx/store'
|
||||
import { Bullet } from '../../../components/Bullet'
|
||||
|
||||
interface Props {
|
||||
item: ISpace
|
||||
activeSpace: Space
|
||||
}
|
||||
|
||||
export function SpaceItem({ item, activeSpace }: Props) {
|
||||
const active = activeSpace.id === item.id
|
||||
return (
|
||||
<PopoverClose asChild>
|
||||
<Box
|
||||
key={item.id}
|
||||
bgGray100={active}
|
||||
bgGray100--hover
|
||||
toCenterY
|
||||
toBetween
|
||||
py3
|
||||
px3
|
||||
gapX2
|
||||
textBase
|
||||
roundedLG
|
||||
cursorPointer
|
||||
transitionColors
|
||||
onClick={async () => {
|
||||
await store.selectSpace(item.id)
|
||||
}}
|
||||
>
|
||||
<Box toCenterY gap2>
|
||||
<Bullet size={20} innerSize={6} innerColor={item.color} />
|
||||
<Box>{item.name}</Box>
|
||||
</Box>
|
||||
{item.isCloud && (
|
||||
<Tag size="sm" variant="light">
|
||||
Cloud
|
||||
</Tag>
|
||||
)}
|
||||
</Box>
|
||||
</PopoverClose>
|
||||
)
|
||||
}
|
||||
@@ -87,6 +87,7 @@ model Space {
|
||||
subdomain String? @unique
|
||||
customDomain String? @unique
|
||||
sort Int @default(0)
|
||||
version Int @default(0)
|
||||
color String
|
||||
isActive Boolean @default(true)
|
||||
activeNodeIds Json?
|
||||
|
||||
@@ -17,7 +17,9 @@
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@penx/constants": "workspace:*",
|
||||
"@penx/model": "workspace:*",
|
||||
"@penx/model-types": "workspace:*",
|
||||
"slate": "0.94.1",
|
||||
"slate-history": "0.93.0",
|
||||
"slate-react": "^0.98.3"
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
export function extractTags(element: any[]): string[] {
|
||||
if (!Array.isArray(element)) return []
|
||||
|
||||
let tags: string[] = []
|
||||
|
||||
for (const item of element) {
|
||||
if (!item?.children?.length) continue
|
||||
const result = item.children
|
||||
.filter((item: any) => item.type === 'tag')
|
||||
.map((i: any) => i.name.replace('#', ''))
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
"compilerOptions": {
|
||||
"lib": ["ESNext", "DOM"]
|
||||
},
|
||||
"include": [".", "fower.d.ts"],
|
||||
"include": [".", "fower.d.ts", "../app/src/common/diffNodes.ts"],
|
||||
"exclude": ["dist", "build", "node_modules"]
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ export function useQueryNodes(spaceId: string) {
|
||||
setNodes(nodes)
|
||||
// console.log('nodes:', nodes)
|
||||
|
||||
if (store.getNode()) return
|
||||
if (!nodes.length) return
|
||||
|
||||
const space = store.getSpaces().find((s) => s.id === spaceId)!
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useRef } from 'react'
|
||||
import { toast } from 'uikit'
|
||||
import { SyncStatus, WorkerEvents } from '@penx/constants'
|
||||
import { db } from '@penx/local-db'
|
||||
import { nodeAtom, spacesAtom, store, syncStatusAtom } from '@penx/store'
|
||||
import { spacesAtom, store, syncStatusAtom } from '@penx/store'
|
||||
|
||||
export function useWorkers() {
|
||||
const workerRef = useRef<Worker>()
|
||||
|
||||
@@ -723,7 +723,6 @@ class DB {
|
||||
if (!cell) {
|
||||
await this.addRow(database.id, ref)
|
||||
}
|
||||
// console.log('========cell:', cell)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ export function getNewSpace(data: Partial<ISpace>): ISpace {
|
||||
id: nanoid(),
|
||||
name: 'My Space',
|
||||
sort: 1,
|
||||
version: 0,
|
||||
isActive: true,
|
||||
isCloud: false,
|
||||
color: getRandomColor(),
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { SettingsType } from '@penx/constants'
|
||||
|
||||
export interface ISpace {
|
||||
id: string
|
||||
|
||||
@@ -17,6 +15,8 @@ export interface ISpace {
|
||||
|
||||
activeNodeIds: string[]
|
||||
|
||||
version: number
|
||||
|
||||
snapshot: {
|
||||
version: number
|
||||
nodeMap: Record<string, string>
|
||||
|
||||
@@ -40,7 +40,9 @@ export class Node {
|
||||
}
|
||||
|
||||
get element(): Element[] {
|
||||
return this.raw.element
|
||||
return Array.isArray(this.raw.element)
|
||||
? this.raw.element
|
||||
: [this.raw.element]
|
||||
}
|
||||
|
||||
get title(): string {
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"dependencies": {
|
||||
"@penx/constants": "workspace:*",
|
||||
"@penx/editor-queries": "workspace:*",
|
||||
"@penx/editor-common": "workspace:*",
|
||||
"@penx/editor-shared": "workspace:*",
|
||||
"@penx/editor-types": "workspace:*",
|
||||
"@penx/model": "workspace:*",
|
||||
|
||||
@@ -2,6 +2,7 @@ import _ from 'lodash'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { createEditor, Editor, Path, Transforms } from 'slate'
|
||||
import { ELEMENT_LI, ELEMENT_LIC } from '@penx/constants'
|
||||
import { extractTags } from '@penx/editor-common'
|
||||
import { getNodeByPath } from '@penx/editor-queries'
|
||||
import { INode, NodeType } from '@penx/model-types'
|
||||
import {
|
||||
@@ -19,6 +20,7 @@ function isListContentElement(node: any): node is ListContentElement {
|
||||
return node?.type === ELEMENT_LIC
|
||||
}
|
||||
|
||||
// TODO: should handle tags
|
||||
export function slateToNodes(
|
||||
node: INode,
|
||||
value: any,
|
||||
@@ -57,11 +59,15 @@ export function slateToNodes(
|
||||
})
|
||||
|
||||
for (const [item, path] of listContents) {
|
||||
// console.log('======item:', item)
|
||||
|
||||
// listItem
|
||||
const parent = getNodeByPath(
|
||||
editor,
|
||||
Path.parent(path),
|
||||
) as any as ListItemElement
|
||||
|
||||
// get node children
|
||||
let children: string[] = []
|
||||
|
||||
if (parent.children.length > 1) {
|
||||
|
||||
@@ -22,29 +22,30 @@
|
||||
"dependencies": {
|
||||
"@penx/autoformat": "workspace:*",
|
||||
"@penx/constants": "workspace:*",
|
||||
"@penx/encryption": "workspace:*",
|
||||
"@penx/editor-queries": "workspace:*",
|
||||
"@penx/editor-common": "workspace:*",
|
||||
"@penx/editor-queries": "workspace:*",
|
||||
"@penx/encryption": "workspace:*",
|
||||
"@penx/extension-typings": "*",
|
||||
"@penx/indexeddb": "workspace:*",
|
||||
"@penx/list": "workspace:*",
|
||||
"@penx/local-db": "workspace:*",
|
||||
"@penx/model": "workspace:*",
|
||||
"@penx/model-types": "workspace:*",
|
||||
"@penx/serializer": "workspace:^",
|
||||
"@penx/store": "workspace:*",
|
||||
"@penx/trpc-client": "workspace:*",
|
||||
"@penx/model-types": "workspace:*",
|
||||
"date-fns": "^2.30.0",
|
||||
"diff-match-patch": "^1.0.5",
|
||||
"immer": "^10.0.2",
|
||||
"jotai": "^2.4.2",
|
||||
"slate-lists": "workspace:*",
|
||||
"ky": "^1.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"mime-types": "^2.1.35",
|
||||
"nanoid": "^4.0.2",
|
||||
"octokit": "^3.1.0",
|
||||
"react-fast-compare": "^3.2.2",
|
||||
"slate": "0.94.1",
|
||||
"slate-lists": "workspace:*",
|
||||
"slate-react": "^0.98.3",
|
||||
"stook": "^1.17.0",
|
||||
"uikit": "workspace:*",
|
||||
|
||||
@@ -90,6 +90,7 @@ export class NodeListService {
|
||||
createTree(node: Node): TreeItem[] {
|
||||
return node.children.map((id) => {
|
||||
const node = this.nodeMap.get(id)!
|
||||
|
||||
if (!node.children.length) {
|
||||
return { ...node.raw, children: [] }
|
||||
}
|
||||
@@ -136,11 +137,19 @@ export class NodeListService {
|
||||
getLinkedReferences(node: Node) {
|
||||
const nodes: Node[] = []
|
||||
|
||||
// console.log('get===this.nodes:', this.nodes)
|
||||
|
||||
for (const item of this.nodes) {
|
||||
// console.log('x=========item:', item)
|
||||
|
||||
if (item.id === node.id) continue
|
||||
if (!item.isCommon) continue
|
||||
|
||||
const isLinked = () => {
|
||||
if (!Array.isArray(item.element)) {
|
||||
console.log('---ite-mmmmmmm:', item, item.element, item.raw.element)
|
||||
}
|
||||
|
||||
const children = item.element.reduce((acc, cur) => {
|
||||
return [...acc, ...cur.children]
|
||||
}, [] as any[])
|
||||
|
||||
@@ -126,10 +126,15 @@ export class NodeService {
|
||||
store.setNodes(nodes)
|
||||
|
||||
if (!isInReference) {
|
||||
// TODO:
|
||||
store.setNode(node)
|
||||
// TODO: should update activeNodes
|
||||
}
|
||||
|
||||
await this.updateSnapshot(node, nodes)
|
||||
|
||||
await new NodeCleaner().cleanDeletedNodes()
|
||||
}
|
||||
|
||||
private async updateSnapshot(node: INode, nodes: INode[]) {
|
||||
// update snapshot
|
||||
const nodeService = new NodeService(
|
||||
new Node(node!),
|
||||
@@ -143,8 +148,6 @@ export class NodeService {
|
||||
|
||||
// update page snapshot
|
||||
await db.updateSnapshot(rootNode.raw, 'update', childrenNodes)
|
||||
|
||||
await new NodeCleaner().cleanDeletedNodes()
|
||||
}
|
||||
|
||||
saveNodes = async (parentId: string, ul: UnorderedListElement) => {
|
||||
|
||||
46
packages/service/src/diffNodes.ts
Normal file
46
packages/service/src/diffNodes.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import isEqual from 'react-fast-compare'
|
||||
import { INode } from '@penx/model-types'
|
||||
|
||||
function isNodeEqual(a: INode, b: INode) {
|
||||
return (
|
||||
isEqual(a.id, b.id) &&
|
||||
isEqual(a.parentId, b.parentId) &&
|
||||
isEqual(a.databaseId, b.databaseId) &&
|
||||
isEqual(a.type, b.type) &&
|
||||
isEqual(a.props, b.props) &&
|
||||
isEqual(a.collapsed, b.collapsed) &&
|
||||
isEqual(a.folded, b.folded) &&
|
||||
isEqual(a.children, b.children) &&
|
||||
isEqual(a.element, b.element)
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: need improve performance
|
||||
export function diffNodes(oldNodes: INode[], newNodes: INode[]) {
|
||||
const added: INode[] = []
|
||||
const updated: INode[] = []
|
||||
const deleted: INode[] = []
|
||||
|
||||
for (const b of newNodes) {
|
||||
const a = oldNodes.find((n) => n.id === b.id)
|
||||
if (!a) {
|
||||
added.push(b)
|
||||
continue
|
||||
}
|
||||
|
||||
// should custom isEqual
|
||||
if (!isNodeEqual(a, b)) {
|
||||
updated.push(b)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for (const a of oldNodes) {
|
||||
const b = newNodes.find((n) => n.id === a.id)
|
||||
if (!b) {
|
||||
deleted.push(a)
|
||||
continue
|
||||
}
|
||||
}
|
||||
return { added, updated, deleted }
|
||||
}
|
||||
@@ -4,3 +4,4 @@ export * from './NodeService'
|
||||
export * from './NodeListService'
|
||||
export * from './NodeCleaner'
|
||||
export * from './ExtensionStore'
|
||||
export * from './diffNodes'
|
||||
|
||||
@@ -14,8 +14,6 @@ import { Command, ExtensionStore, RouteName, RouterStore } from './types'
|
||||
|
||||
export const spacesAtom = atom<ISpace[]>([])
|
||||
|
||||
export const nodeAtom = atom(null as any as INode)
|
||||
|
||||
export const nodesAtom = atom<INode[]>([])
|
||||
|
||||
export const activeNodesAtom = atom<INode[]>([])
|
||||
@@ -87,10 +85,6 @@ export const store = Object.assign(createStore(), {
|
||||
store.set(editorsAtom, editors)
|
||||
},
|
||||
|
||||
getNode() {
|
||||
return store.get(nodeAtom)
|
||||
},
|
||||
|
||||
findNode(id: string) {
|
||||
const nodes = store.getNodes()
|
||||
return nodes.find((node) => node.id === id)
|
||||
@@ -113,10 +107,6 @@ export const store = Object.assign(createStore(), {
|
||||
return cells
|
||||
},
|
||||
|
||||
setNode(node: INode) {
|
||||
return store.set(nodeAtom, node)
|
||||
},
|
||||
|
||||
getUser() {
|
||||
return store.get(userAtom)
|
||||
},
|
||||
@@ -134,21 +124,24 @@ export const store = Object.assign(createStore(), {
|
||||
})
|
||||
},
|
||||
|
||||
async trashNode(id: string) {
|
||||
//
|
||||
},
|
||||
|
||||
async selectNode(node: INode, index = 0) {
|
||||
const router = store.get(routerAtom)
|
||||
if (router.name !== 'NODE') this.routeTo('NODE')
|
||||
|
||||
const activeNodes = store.getActiveNodes()
|
||||
|
||||
if (index === 0 && activeNodes[0]?.id === node.id) {
|
||||
console.log('is equal node')
|
||||
return
|
||||
}
|
||||
|
||||
const editor = store.getEditor(index)
|
||||
clearEditor(editor)
|
||||
|
||||
const nodes = store.getNodes()
|
||||
const value = nodeToSlate(node, nodes)
|
||||
Transforms.insertNodes(editor, value)
|
||||
|
||||
Transforms.insertNodes(editor, value)
|
||||
const newActiveNodes = this.setFirstActiveNodes(node)
|
||||
await db.updateSpace(this.getActiveSpace().id, {
|
||||
activeNodeIds: newActiveNodes.map((node) => node.id),
|
||||
|
||||
12
pnpm-lock.yaml
generated
12
pnpm-lock.yaml
generated
@@ -3088,9 +3088,15 @@ importers:
|
||||
|
||||
packages/editor-common:
|
||||
dependencies:
|
||||
'@penx/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../constants
|
||||
'@penx/model':
|
||||
specifier: workspace:*
|
||||
version: link:../model
|
||||
'@penx/model-types':
|
||||
specifier: workspace:*
|
||||
version: link:../model-types
|
||||
slate:
|
||||
specifier: 0.94.1
|
||||
version: 0.94.1
|
||||
@@ -4054,6 +4060,9 @@ importers:
|
||||
'@penx/constants':
|
||||
specifier: workspace:*
|
||||
version: link:../constants
|
||||
'@penx/editor-common':
|
||||
specifier: workspace:*
|
||||
version: link:../editor-common
|
||||
'@penx/editor-queries':
|
||||
specifier: workspace:*
|
||||
version: link:../editor-queries
|
||||
@@ -4175,6 +4184,9 @@ importers:
|
||||
octokit:
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0
|
||||
react-fast-compare:
|
||||
specifier: ^3.2.2
|
||||
version: 3.2.2
|
||||
slate:
|
||||
specifier: 0.94.1
|
||||
version: 0.94.1
|
||||
|
||||
Reference in New Issue
Block a user