mirror of
https://github.com/QingWei-Li/notea.git
synced 2024-12-04 20:32:36 +03:00
refactor: tree api
This commit is contained in:
parent
4412e8b33b
commit
b6edd1a5d5
@ -2,12 +2,11 @@ import SidebarListItem from './sidebar-list-item'
|
||||
import { NoteTreeState, TreeModel } from 'containers/tree'
|
||||
import Tree, {
|
||||
ItemId,
|
||||
moveItemOnTree,
|
||||
mutateTree,
|
||||
TreeDestinationPosition,
|
||||
TreeSourcePosition,
|
||||
} from '@atlaskit/tree'
|
||||
import { useState, useEffect, useCallback } from 'react'
|
||||
import { useEffect, useCallback } from 'react'
|
||||
import { NoteState } from 'containers/note'
|
||||
import IconPlus from 'heroicons/react/outline/Plus'
|
||||
import router from 'next/router'
|
||||
@ -15,9 +14,8 @@ import HotkeyTooltip from 'components/hotkey-tooltip'
|
||||
import SidebarItemButton from './sidebar-item-button'
|
||||
|
||||
const SideBarList = () => {
|
||||
const { tree, updateTree, initTree } = NoteTreeState.useContainer()
|
||||
const { updateNoteMeta, initAllNotes } = NoteState.useContainer()
|
||||
const [curId, setCurId] = useState<ItemId>(0)
|
||||
const { tree, updateTree, initTree, moveTree } = NoteTreeState.useContainer()
|
||||
const { initAllNotes } = NoteState.useContainer()
|
||||
|
||||
useEffect(() => {
|
||||
initTree().then(() => initAllNotes())
|
||||
@ -39,33 +37,15 @@ const SideBarList = () => {
|
||||
|
||||
const onDragEnd = useCallback(
|
||||
(source: TreeSourcePosition, destination?: TreeDestinationPosition) => {
|
||||
if (!destination) {
|
||||
return
|
||||
}
|
||||
const newTree = moveItemOnTree(tree, source, destination) as TreeModel
|
||||
const toPid = destination.parentId as string
|
||||
const fromPid = source.parentId as string
|
||||
|
||||
updateTree(newTree)
|
||||
|
||||
Promise.all([
|
||||
newTree.items[curId].data.pid !== toPid &&
|
||||
updateNoteMeta(curId as string, {
|
||||
pid: toPid,
|
||||
}),
|
||||
updateNoteMeta(toPid, {
|
||||
cid: newTree.items[toPid].children,
|
||||
}),
|
||||
fromPid !== toPid &&
|
||||
updateNoteMeta(fromPid, {
|
||||
cid: newTree.items[fromPid].children,
|
||||
}),
|
||||
]).catch((e) => {
|
||||
moveTree({
|
||||
source,
|
||||
destination,
|
||||
}).catch((e) => {
|
||||
// todo: toast
|
||||
console.error('更新错误', e)
|
||||
})
|
||||
},
|
||||
[curId, tree, updateNoteMeta, updateTree]
|
||||
[moveTree]
|
||||
)
|
||||
|
||||
return (
|
||||
@ -87,7 +67,6 @@ const SideBarList = () => {
|
||||
onExpand={onExpand}
|
||||
onCollapse={onCollapse}
|
||||
onDragEnd={onDragEnd}
|
||||
onDragStart={setCurId}
|
||||
tree={tree}
|
||||
isDragEnabled
|
||||
isNestingEnabled
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { createContainer } from 'unstated-next'
|
||||
import useFetch from 'use-http'
|
||||
import { wrap, Remote } from 'comlink'
|
||||
import { NoteWorkerApi } from 'workers/note.worker'
|
||||
import { NoteTreeState } from 'containers/tree'
|
||||
import { NOTE_DELETED, NOTE_SHARED } from 'shared/meta'
|
||||
import { useNoteWorker } from 'workers/note'
|
||||
|
||||
export interface NoteModel {
|
||||
id: string
|
||||
@ -22,19 +21,7 @@ const useNote = () => {
|
||||
const [note, setNote] = useState<NoteModel>({} as NoteModel)
|
||||
const { get, post, cache, abort, loading } = useFetch('/api/notes')
|
||||
const { addToTree, removeFromTree } = NoteTreeState.useContainer()
|
||||
|
||||
const NoteWorkerRef = useRef<Worker>()
|
||||
const NoteWorkerApiRef = useRef<Remote<NoteWorkerApi>>()
|
||||
|
||||
useEffect(() => {
|
||||
NoteWorkerRef.current = new Worker('workers/note.worker', {
|
||||
type: 'module',
|
||||
})
|
||||
NoteWorkerApiRef.current = wrap(NoteWorkerRef.current)
|
||||
return () => {
|
||||
NoteWorkerRef.current?.terminate()
|
||||
}
|
||||
}, [])
|
||||
const noteWorker = useNoteWorker()
|
||||
|
||||
const getById = useCallback(
|
||||
async (id: string) => {
|
||||
@ -84,14 +71,14 @@ const useNote = () => {
|
||||
...result,
|
||||
}
|
||||
|
||||
NoteWorkerApiRef.current?.saveNote(newNote.id, newNote)
|
||||
|
||||
noteWorker.current?.saveNote(newNote.id, newNote)
|
||||
delete newNote.content
|
||||
setNote(newNote)
|
||||
addToTree(newNote)
|
||||
|
||||
return newNote
|
||||
},
|
||||
[abort, addToTree, cache, note, post]
|
||||
[abort, addToTree, cache, note, noteWorker, post]
|
||||
)
|
||||
|
||||
const updateNoteMeta = useCallback(
|
||||
@ -110,8 +97,8 @@ const useNote = () => {
|
||||
)
|
||||
|
||||
const initAllNotes = useCallback(() => {
|
||||
NoteWorkerApiRef.current?.checkAllNotes()
|
||||
}, [])
|
||||
noteWorker.current?.checkAllNotes()
|
||||
}, [noteWorker])
|
||||
|
||||
const removeNote = useCallback(
|
||||
async (id: string) => {
|
||||
|
@ -1,15 +1,24 @@
|
||||
import { mutateTree, TreeData, TreeItem } from '@atlaskit/tree'
|
||||
import { isEmpty, forEach } from 'lodash'
|
||||
import {
|
||||
moveItemOnTree,
|
||||
mutateTree,
|
||||
TreeData,
|
||||
TreeDestinationPosition,
|
||||
TreeItem,
|
||||
TreeSourcePosition,
|
||||
} from '@atlaskit/tree'
|
||||
import { isEmpty, forEach, union, map } from 'lodash'
|
||||
import { genId } from 'packages/shared'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { NOTE_DELETED } from 'shared/meta'
|
||||
import { createContainer } from 'unstated-next'
|
||||
import { uiStore } from 'utils/local-store'
|
||||
import { NoteModel } from './note'
|
||||
import { useNoteWorker } from 'workers/note'
|
||||
import useFetch from 'use-http'
|
||||
|
||||
export interface TreeItemModel extends TreeItem {
|
||||
id: string
|
||||
data: NoteModel
|
||||
data?: NoteModel
|
||||
children: string[]
|
||||
}
|
||||
export interface TreeModel extends TreeData {
|
||||
@ -17,13 +26,12 @@ export interface TreeModel extends TreeData {
|
||||
items: Record<string, TreeItemModel>
|
||||
}
|
||||
|
||||
const DEFAULT_TREE: TreeModel = {
|
||||
export const DEFAULT_TREE: TreeModel = {
|
||||
rootId: 'root',
|
||||
items: {
|
||||
root: {
|
||||
id: 'root',
|
||||
children: [],
|
||||
data: {} as NoteModel,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -35,9 +43,6 @@ const saveLocalTree = (data: TreeModel) => {
|
||||
items[item.id] = {
|
||||
isExpanded: item.isExpanded,
|
||||
id: item.id,
|
||||
data: {
|
||||
date: item.data.date,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
@ -48,7 +53,9 @@ const saveLocalTree = (data: TreeModel) => {
|
||||
}
|
||||
|
||||
const useNoteTree = (initData: TreeModel = DEFAULT_TREE) => {
|
||||
const { post } = useFetch('/api/tree')
|
||||
const [tree, setTree] = useState<TreeModel>(initData)
|
||||
const noteWorker = useNoteWorker()
|
||||
const treeRef = useRef(tree)
|
||||
|
||||
useEffect(() => {
|
||||
@ -63,37 +70,36 @@ const useNoteTree = (initData: TreeModel = DEFAULT_TREE) => {
|
||||
const initTree = useCallback(async () => {
|
||||
const localTree =
|
||||
(await uiStore.getItem<TreeModel>('tree_items')) || DEFAULT_TREE
|
||||
const curTree = treeRef.current
|
||||
const newItems = {} as TreeModel['items']
|
||||
|
||||
setTree((prev) => {
|
||||
const deletedIds: string[] = []
|
||||
forEach(prev.items, (item) => {
|
||||
await Promise.all(
|
||||
map(curTree.items, async (item) => {
|
||||
if (!item.isExpanded && localTree.items[item.id]?.isExpanded) {
|
||||
item.isExpanded = true
|
||||
}
|
||||
if (item.data.deleted) {
|
||||
deletedIds.push(item.id)
|
||||
|
||||
newItems[item.id] = {
|
||||
...item,
|
||||
data: await noteWorker.current?.fetchNote(item.id),
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
saveLocalTree(prev)
|
||||
|
||||
return prev
|
||||
updateTree({
|
||||
...curTree,
|
||||
items: newItems,
|
||||
})
|
||||
}, [])
|
||||
}, [noteWorker, updateTree])
|
||||
|
||||
const addToTree = useCallback(
|
||||
(item: NoteModel) => {
|
||||
const newItems: TreeModel['items'] = {}
|
||||
const curTree = treeRef.current
|
||||
const parentItem = curTree.items[item.pid || 'root']
|
||||
const curItem = curTree.items[item.id]
|
||||
const parentItem = treeRef.current.items[item.pid || 'root']
|
||||
|
||||
if (!parentItem.children.includes(item.id)) {
|
||||
newItems[parentItem.id] = {
|
||||
...parentItem,
|
||||
children: [...parentItem.children, item.id],
|
||||
}
|
||||
}
|
||||
parentItem.children = union(parentItem.children, [item.id])
|
||||
|
||||
if (!curItem) {
|
||||
newItems[item.id] = {
|
||||
@ -101,7 +107,7 @@ const useNoteTree = (initData: TreeModel = DEFAULT_TREE) => {
|
||||
data: item,
|
||||
children: [],
|
||||
}
|
||||
} else if (curItem.data.title !== item.title) {
|
||||
} else if (curItem.data?.title !== item.title) {
|
||||
newItems[item.id] = {
|
||||
...curItem,
|
||||
data: item,
|
||||
@ -143,6 +149,29 @@ const useNoteTree = (initData: TreeModel = DEFAULT_TREE) => {
|
||||
return newId
|
||||
}, [])
|
||||
|
||||
const moveTree = useCallback(
|
||||
async (data: {
|
||||
source: TreeSourcePosition
|
||||
destination?: TreeDestinationPosition
|
||||
}) => {
|
||||
if (!data.destination) {
|
||||
return
|
||||
}
|
||||
|
||||
const newTree = moveItemOnTree(
|
||||
treeRef.current,
|
||||
data.source,
|
||||
data.destination
|
||||
)
|
||||
|
||||
updateTree(newTree as TreeModel)
|
||||
await post('move', data)
|
||||
|
||||
return
|
||||
},
|
||||
[post, updateTree]
|
||||
)
|
||||
|
||||
return {
|
||||
tree,
|
||||
addToTree,
|
||||
@ -150,6 +179,7 @@ const useNoteTree = (initData: TreeModel = DEFAULT_TREE) => {
|
||||
updateTree,
|
||||
initTree,
|
||||
genNewId,
|
||||
moveTree,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
export class StorePath {
|
||||
prefix: string
|
||||
|
||||
constructor(prefix = '') {
|
||||
this.prefix = prefix
|
||||
}
|
||||
|
||||
getNoteIndex() {
|
||||
return `note_index`
|
||||
}
|
||||
|
||||
getNoteById(id: string) {
|
||||
return `notes/${id}`
|
||||
}
|
||||
|
||||
getFileByName(name: string) {
|
||||
return `files/${name}`
|
||||
}
|
||||
|
||||
getPath(...paths: string[]) {
|
||||
return this.prefix + paths.join('/')
|
||||
}
|
||||
}
|
@ -1,6 +1,3 @@
|
||||
import { pull, union } from 'lodash'
|
||||
import { StorePath } from '../path'
|
||||
|
||||
export interface StoreProviderConfig {
|
||||
prefix?: string
|
||||
}
|
||||
@ -18,12 +15,13 @@ export interface ObjectOptions {
|
||||
export abstract class StoreProvider {
|
||||
constructor({ prefix }: StoreProviderConfig) {
|
||||
this.prefix = prefix
|
||||
this.path = new StorePath(prefix)
|
||||
}
|
||||
|
||||
prefix?: string
|
||||
|
||||
path: StorePath
|
||||
getPath(...paths: string[]) {
|
||||
return this.prefix + paths.join('/')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签名 URL
|
||||
@ -86,39 +84,4 @@ export abstract class StoreProvider {
|
||||
toPath: string,
|
||||
options: ObjectOptions
|
||||
): Promise<void>
|
||||
|
||||
/**
|
||||
* 页面列表
|
||||
* - 私有读
|
||||
*/
|
||||
async getList() {
|
||||
const content = (await this.getObject(this.path.getNoteIndex())) || ''
|
||||
const list = content.split(',').filter(Boolean)
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
/**
|
||||
* 增加到列表
|
||||
*/
|
||||
async addToList(noteIds: string[]) {
|
||||
const indexPath = this.path.getNoteIndex()
|
||||
let content = (await this.getObject(indexPath)) || ''
|
||||
const ids = content.split(',')
|
||||
|
||||
content = union(ids, noteIds).filter(Boolean).join(',')
|
||||
await this.putObject(indexPath, content)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从列表移除
|
||||
*/
|
||||
async removeFromList(noteIds: string[]) {
|
||||
const indexPath = this.path.getNoteIndex()
|
||||
let content = (await this.getObject(indexPath)) || ''
|
||||
const ids = content.split(',')
|
||||
|
||||
content = pull(ids, ...noteIds).join(',')
|
||||
await this.putObject(indexPath, content)
|
||||
}
|
||||
}
|
||||
|
@ -40,12 +40,12 @@ export class StoreS3 extends StoreProvider {
|
||||
}
|
||||
|
||||
async getSignUrl(path: string) {
|
||||
return this.store.signatureUrl(this.path.getPath(path))
|
||||
return this.store.signatureUrl(this.getPath(path))
|
||||
}
|
||||
|
||||
async hasObject(path: string) {
|
||||
try {
|
||||
const data = await this.store.head(this.path.getPath(path))
|
||||
const data = await this.store.head(this.getPath(path))
|
||||
|
||||
return !!data
|
||||
} catch (e) {
|
||||
@ -57,7 +57,7 @@ export class StoreS3 extends StoreProvider {
|
||||
let content
|
||||
|
||||
try {
|
||||
const result = await this.store.getAsBuffer(this.path.getPath(path))
|
||||
const result = await this.store.getAsBuffer(this.getPath(path))
|
||||
content = result?.content
|
||||
} catch (err) {
|
||||
if (err.code !== 'NoSuchKey') {
|
||||
@ -70,7 +70,7 @@ export class StoreS3 extends StoreProvider {
|
||||
|
||||
async getObjectMeta(path: string) {
|
||||
try {
|
||||
const result = await this.store.head(this.path.getPath(path))
|
||||
const result = await this.store.head(this.getPath(path))
|
||||
return result || undefined
|
||||
} catch (err) {
|
||||
if (err.code !== 'NoSuchKey') {
|
||||
@ -89,10 +89,7 @@ export class StoreS3 extends StoreProvider {
|
||||
let meta
|
||||
|
||||
try {
|
||||
const result = await this.store.getAsBuffer(
|
||||
this.path.getPath(path),
|
||||
metaKeys
|
||||
)
|
||||
const result = await this.store.getAsBuffer(this.getPath(path), metaKeys)
|
||||
content = result?.content
|
||||
meta = result?.meta
|
||||
} catch (err) {
|
||||
@ -111,21 +108,17 @@ export class StoreS3 extends StoreProvider {
|
||||
isCompressed?: boolean
|
||||
) {
|
||||
await this.store.put(
|
||||
this.path.getPath(path),
|
||||
this.getPath(path),
|
||||
isBuffer(raw) ? raw : toBuffer(raw, isCompressed),
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
async deleteObject(path: string) {
|
||||
await this.store.del(this.path.getPath(path))
|
||||
await this.store.del(this.getPath(path))
|
||||
}
|
||||
|
||||
async copyObject(fromPath: string, toPath: string, options: ObjectOptions) {
|
||||
await this.store.copy(
|
||||
this.path.getPath(toPath),
|
||||
this.path.getPath(fromPath),
|
||||
options
|
||||
)
|
||||
await this.store.copy(this.getPath(toPath), this.getPath(fromPath), options)
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { useStore } from 'services/middlewares/store'
|
||||
import { IncomingForm } from 'formidable'
|
||||
import { readFileSync } from 'fs'
|
||||
import dayjs from 'dayjs'
|
||||
import { getPathFileByName } from 'services/note-path'
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
@ -26,7 +27,7 @@ export default api()
|
||||
|
||||
const file = data.files.file
|
||||
const buffer = readFileSync(file.path)
|
||||
const filePath = req.store.path.getFileByName(
|
||||
const filePath = getPathFileByName(
|
||||
`${dayjs().format('YYYY/MM/DD')}/${file.name}`
|
||||
)
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { api } from 'services/api'
|
||||
import { metaToJson } from 'services/meta'
|
||||
import { useAuth } from 'services/middlewares/auth'
|
||||
import { useStore } from 'services/middlewares/store'
|
||||
import { getPathNoteById } from 'services/note-path'
|
||||
import { PAGE_META_KEY } from 'shared/meta'
|
||||
|
||||
export default api()
|
||||
@ -10,12 +11,11 @@ export default api()
|
||||
.use(useStore)
|
||||
.delete(async (req, res) => {
|
||||
const id = req.query.id as string
|
||||
const notePath = req.store.path.getNoteById(id)
|
||||
const notePath = getPathNoteById(id)
|
||||
|
||||
// todo 父节点的 cid 也要清空
|
||||
await Promise.all([
|
||||
req.store.deleteObject(notePath),
|
||||
req.store.removeFromList([id]),
|
||||
req.treeStore.removeItem(id),
|
||||
])
|
||||
|
||||
res.end()
|
||||
@ -24,7 +24,7 @@ export default api()
|
||||
const id = req.query.id as string
|
||||
|
||||
const { content, meta } = await req.store.getObjectAndMeta(
|
||||
req.store.path.getNoteById(id),
|
||||
getPathNoteById(id),
|
||||
PAGE_META_KEY
|
||||
)
|
||||
|
||||
@ -41,7 +41,7 @@ export default api()
|
||||
.post(async (req, res) => {
|
||||
const id = req.query.id as string
|
||||
const { content } = req.body
|
||||
const notePath = req.store.path.getNoteById(id)
|
||||
const notePath = getPathNoteById(id)
|
||||
const oldMeta = await req.store.getObjectMeta(notePath)
|
||||
|
||||
if (oldMeta) {
|
||||
|
@ -2,13 +2,14 @@ import { api } from 'services/api'
|
||||
import { jsonToMeta, metaToJson } from 'services/meta'
|
||||
import { useAuth } from 'services/middlewares/auth'
|
||||
import { useStore } from 'services/middlewares/store'
|
||||
import { getPathNoteById } from 'services/note-path'
|
||||
|
||||
export default api()
|
||||
.use(useAuth)
|
||||
.use(useStore)
|
||||
.post(async (req, res) => {
|
||||
const id = req.body.id || req.query.id
|
||||
const notePath = req.store.path.getNoteById(id)
|
||||
const notePath = getPathNoteById(id)
|
||||
const oldMeta = await req.store.getObjectMeta(notePath)
|
||||
let meta = jsonToMeta({
|
||||
...req.body,
|
||||
@ -28,7 +29,7 @@ export default api()
|
||||
})
|
||||
.get(async (req, res) => {
|
||||
const id = req.body.id || req.query.id
|
||||
const notePath = req.store.path.getNoteById(id)
|
||||
const notePath = getPathNoteById(id)
|
||||
const meta = await req.store.getObjectMeta(notePath)
|
||||
|
||||
res.json(metaToJson(meta))
|
||||
|
@ -1,22 +1,17 @@
|
||||
import { genId } from '@notea/shared'
|
||||
import { api } from 'services/api'
|
||||
import { getTree } from 'services/get-tree'
|
||||
import { jsonToMeta, metaToJson } from 'services/meta'
|
||||
import { jsonToMeta } from 'services/meta'
|
||||
import { useAuth } from 'services/middlewares/auth'
|
||||
import { useStore } from 'services/middlewares/store'
|
||||
import { getPathNoteById } from 'services/note-path'
|
||||
|
||||
export default api()
|
||||
.use(useAuth)
|
||||
.use(useStore)
|
||||
.get(async (req, res) => {
|
||||
const tree = await getTree(req.store)
|
||||
|
||||
res.json(tree)
|
||||
})
|
||||
.post(async (req, res) => {
|
||||
const { content = '\n', meta } = req.body
|
||||
let id = req.body.id as string
|
||||
const notePath = req.store.path.getNoteById(id)
|
||||
const notePath = getPathNoteById(id)
|
||||
|
||||
if (!id) {
|
||||
id = genId()
|
||||
@ -36,20 +31,7 @@ export default api()
|
||||
contentType: 'text/markdown',
|
||||
meta: metaData,
|
||||
})
|
||||
|
||||
await req.store.addToList([id])
|
||||
// Update parent meta
|
||||
const parentPath = req.store.path.getNoteById(meta.pid || 'root')
|
||||
const parentMeta = metaToJson(await req.store.getObjectMeta(parentPath))
|
||||
const cid = (parentMeta.cid || []).concat(id)
|
||||
const newParentMeta = jsonToMeta({
|
||||
...parentMeta,
|
||||
cid: [...new Set(cid)].toString(),
|
||||
})
|
||||
|
||||
await req.store.copyObject(parentPath, parentPath, {
|
||||
meta: newParentMeta,
|
||||
})
|
||||
await req.treeStore.addItem(id, meta.pid)
|
||||
|
||||
res.json(metaWithModel)
|
||||
})
|
||||
|
10
pages/api/tree/index.ts
Normal file
10
pages/api/tree/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { api } from 'services/api'
|
||||
import { useAuth } from 'services/middlewares/auth'
|
||||
import { useStore } from 'services/middlewares/store'
|
||||
|
||||
export default api()
|
||||
.use(useAuth)
|
||||
.use(useStore)
|
||||
.get(async (req, res) => {
|
||||
res.json(await req.treeStore.get())
|
||||
})
|
14
pages/api/tree/move.ts
Normal file
14
pages/api/tree/move.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { api } from 'services/api'
|
||||
import { useAuth } from 'services/middlewares/auth'
|
||||
import { useStore } from 'services/middlewares/store'
|
||||
|
||||
export default api()
|
||||
.use(useAuth)
|
||||
.use(useStore)
|
||||
.post(async (req, res) => {
|
||||
const { source, destination } = req.body
|
||||
|
||||
await req.treeStore.moveItem(source, destination)
|
||||
|
||||
res.end()
|
||||
})
|
@ -5,9 +5,11 @@ import { API } from './middlewares/error'
|
||||
import { StoreProvider } from '@notea/store'
|
||||
import { useSession } from './middlewares/session'
|
||||
import { Session } from 'next-iron-session'
|
||||
import { TreeStore } from './tree'
|
||||
|
||||
export type ApiRequest = NextApiRequest & {
|
||||
store: StoreProvider
|
||||
treeStore: TreeStore
|
||||
session: Session
|
||||
}
|
||||
|
||||
|
@ -1,40 +0,0 @@
|
||||
import { NoteModel } from 'containers/note'
|
||||
import { TreeModel } from 'containers/tree'
|
||||
import { StoreProvider } from 'packages/store/src'
|
||||
import { metaToJson } from './meta'
|
||||
|
||||
export async function getTree(store: StoreProvider) {
|
||||
const list = await store.getList()
|
||||
const tree: TreeModel = {
|
||||
rootId: 'root',
|
||||
items: {},
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
list.map(async (id) => {
|
||||
const metaData = await store.getObjectMeta(store.path.getNoteById(id))
|
||||
const { cid, ...meta } = metaToJson(metaData)
|
||||
|
||||
delete meta.id
|
||||
tree.items[id] = {
|
||||
id,
|
||||
data: meta as NoteModel,
|
||||
children: cid || [],
|
||||
}
|
||||
|
||||
return
|
||||
})
|
||||
)
|
||||
|
||||
if (!list.includes('root')) {
|
||||
await store.putObject(store.path.getNoteById('root'), '')
|
||||
await store.addToList(['root'])
|
||||
tree.items['root'] = {
|
||||
id: 'root',
|
||||
children: [],
|
||||
data: {} as NoteModel,
|
||||
}
|
||||
}
|
||||
|
||||
return tree
|
||||
}
|
@ -2,7 +2,6 @@ import { isNil, toNumber } from 'lodash'
|
||||
import { strCompress, strDecompress } from 'packages/shared'
|
||||
import {
|
||||
PAGE_META_KEY,
|
||||
ARRAY_KEYS,
|
||||
NOTE_DELETED,
|
||||
NOTE_SHARED,
|
||||
NUMBER_KEYS,
|
||||
@ -34,9 +33,7 @@ export function metaToJson(metaData?: Map<string, string>) {
|
||||
if (!isNil(value)) {
|
||||
const strValue = strDecompress(value) || undefined
|
||||
|
||||
if (ARRAY_KEYS.includes(key)) {
|
||||
meta[key] = strValue.split(',') || []
|
||||
} else if (NUMBER_KEYS.includes(key)) {
|
||||
if (NUMBER_KEYS.includes(key)) {
|
||||
meta[key] = toNumber(strValue)
|
||||
} else {
|
||||
meta[key] = strValue
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createStore } from '@notea/store'
|
||||
import { ApiRequest, ApiResponse, ApiNext } from '../api'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import { TreeStore } from 'services/tree'
|
||||
|
||||
export function useStore(req: ApiRequest, _res: ApiResponse, next: ApiNext) {
|
||||
applyStore(req)
|
||||
@ -10,6 +11,7 @@ export function useStore(req: ApiRequest, _res: ApiResponse, next: ApiNext) {
|
||||
|
||||
function applyStore(req: ApiRequest) {
|
||||
req.store = createStore()
|
||||
req.treeStore = new TreeStore(req.store)
|
||||
}
|
||||
|
||||
export function withStore(wrapperHandler: any) {
|
||||
|
11
services/note-path.ts
Normal file
11
services/note-path.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export function getPathTree() {
|
||||
return `tree`
|
||||
}
|
||||
|
||||
export function getPathNoteById(id: string) {
|
||||
return `notes/${id}`
|
||||
}
|
||||
|
||||
export function getPathFileByName(name: string) {
|
||||
return `files/${name}`
|
||||
}
|
74
services/tree.ts
Normal file
74
services/tree.ts
Normal file
@ -0,0 +1,74 @@
|
||||
import { moveItemOnTree } from '@atlaskit/tree'
|
||||
import { DEFAULT_TREE, TreeModel } from 'containers/tree'
|
||||
import { forEach, pull, union } from 'lodash'
|
||||
import { StoreProvider } from 'packages/store/src'
|
||||
import { getPathTree } from './note-path'
|
||||
|
||||
interface movePosition {
|
||||
parentId: string
|
||||
index: number
|
||||
}
|
||||
|
||||
export class TreeStore {
|
||||
store: StoreProvider
|
||||
treePath: string
|
||||
|
||||
constructor(store: StoreProvider) {
|
||||
this.store = store
|
||||
this.treePath = getPathTree()
|
||||
}
|
||||
|
||||
async get() {
|
||||
const res = await this.store.getObject(this.treePath)
|
||||
|
||||
if (!res) {
|
||||
return this.set(DEFAULT_TREE)
|
||||
}
|
||||
|
||||
return JSON.parse(res) as TreeModel
|
||||
}
|
||||
|
||||
async set(tree: TreeModel) {
|
||||
await this.store.putObject(this.treePath, JSON.stringify(tree))
|
||||
|
||||
return tree
|
||||
}
|
||||
|
||||
async addItem(id: string, parentId = 'root') {
|
||||
const tree = await this.get()
|
||||
|
||||
tree.items[id] = {
|
||||
id,
|
||||
children: [],
|
||||
}
|
||||
|
||||
const parentItem = tree.items[parentId]
|
||||
|
||||
parentItem.children = union(parentItem.children, [id])
|
||||
|
||||
return this.set(tree)
|
||||
}
|
||||
|
||||
async removeItem(id: string) {
|
||||
const tree = await this.get()
|
||||
|
||||
forEach(tree.items, (item) => {
|
||||
if (item.children.includes(id)) {
|
||||
pull(item.children, id)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
return this.set(tree)
|
||||
}
|
||||
|
||||
async moveItem(source: movePosition, destination: movePosition) {
|
||||
const tree = moveItemOnTree(
|
||||
await this.get(),
|
||||
source,
|
||||
destination
|
||||
) as TreeModel
|
||||
|
||||
return this.set(tree)
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import { ApiRequest } from './api'
|
||||
import { getTree } from './get-tree'
|
||||
import { API } from './middlewares/error'
|
||||
import { withSession } from './middlewares/session'
|
||||
import { withStore } from './middlewares/store'
|
||||
@ -30,7 +29,7 @@ export default function withTree(wrapperHandler: any) {
|
||||
let tree
|
||||
|
||||
try {
|
||||
tree = await getTree(ctx.req.store)
|
||||
tree = await ctx.req.treeStore.get()
|
||||
} catch (error) {
|
||||
return API.NOT_FOUND.throw(error.message)
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ type PAGE_META_KEY =
|
||||
| 'pid'
|
||||
| 'id'
|
||||
| 'shared'
|
||||
| 'cid'
|
||||
| 'pic'
|
||||
| 'date'
|
||||
| 'deleted'
|
||||
@ -23,12 +22,9 @@ export const PAGE_META_KEY: PAGE_META_KEY[] = [
|
||||
'pid',
|
||||
'id',
|
||||
'shared',
|
||||
'cid',
|
||||
'pic',
|
||||
'date',
|
||||
'deleted',
|
||||
]
|
||||
|
||||
export const ARRAY_KEYS: PAGE_META_KEY[] = ['cid']
|
||||
|
||||
export const NUMBER_KEYS: PAGE_META_KEY[] = ['deleted', 'shared']
|
||||
|
20
workers/note.ts
Normal file
20
workers/note.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { wrap, Remote } from 'comlink'
|
||||
import { NoteWorkerApi } from './note.worker'
|
||||
import { useRef, useEffect } from 'react'
|
||||
|
||||
export function useNoteWorker() {
|
||||
const NoteWorkerRef = useRef<Worker>()
|
||||
const NoteWorkerApiRef = useRef<Remote<NoteWorkerApi>>()
|
||||
|
||||
useEffect(() => {
|
||||
NoteWorkerRef.current = new Worker('workers/note.worker', {
|
||||
type: 'module',
|
||||
})
|
||||
NoteWorkerApiRef.current = wrap(NoteWorkerRef.current)
|
||||
return () => {
|
||||
NoteWorkerRef.current?.terminate()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return NoteWorkerApiRef
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
import { expose } from 'comlink'
|
||||
import { noteStore, NoteStoreItem, uiStore } from 'utils/local-store'
|
||||
import { map, pull } from 'lodash'
|
||||
import { keys, pull } from 'lodash'
|
||||
import { NoteModel } from 'containers/note'
|
||||
import dayjs from 'dayjs'
|
||||
import removeMarkdown from 'remove-markdown'
|
||||
import { TreeModel } from 'containers/tree'
|
||||
|
||||
@ -18,6 +17,9 @@ const noteWorker: NoteWorkerApi = {
|
||||
saveNote,
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除本地存储中未使用的 note
|
||||
*/
|
||||
async function checkAllNotes() {
|
||||
const tree = await uiStore.getItem<TreeModel>('tree_items')
|
||||
|
||||
@ -25,10 +27,7 @@ async function checkAllNotes() {
|
||||
|
||||
delete tree.items.root
|
||||
|
||||
const notes = await Promise.all(
|
||||
map(tree.items, async (item) => fetchNote(item.id, item.data.date))
|
||||
)
|
||||
const noteIds = notes.map((n) => n?.id)
|
||||
const noteIds = keys(tree.items)
|
||||
const localNoteIds = await noteStore.keys()
|
||||
const unusedNoteIds = pull(localNoteIds, ...noteIds)
|
||||
|
||||
@ -37,11 +36,17 @@ async function checkAllNotes() {
|
||||
)
|
||||
}
|
||||
|
||||
async function fetchNote(id: string, expiredDate?: string) {
|
||||
export async function fetchNote(id: string) {
|
||||
if (id === 'root') {
|
||||
return {
|
||||
id: 'root',
|
||||
} as NoteStoreItem
|
||||
}
|
||||
|
||||
const note = await noteStore.getItem<NoteStoreItem>(id)
|
||||
|
||||
if (note && expiredDate && dayjs(note.date).isSame(expiredDate)) {
|
||||
return
|
||||
if (note) {
|
||||
return note
|
||||
}
|
||||
|
||||
const res = await fetch(`/api/notes/${id}`)
|
||||
|
Loading…
Reference in New Issue
Block a user