fix(workbench): complete profile and channel flows
This commit is contained in:
@@ -33,8 +33,6 @@ import ReactMarkdown from 'react-markdown'
|
||||
import rehypeSanitize from 'rehype-sanitize'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import DOMPurify from 'dompurify'
|
||||
import mammoth from 'mammoth'
|
||||
import { readSheet as readXlsxSheet } from 'read-excel-file/browser'
|
||||
import type { ApiSession } from '../../api/client'
|
||||
import {
|
||||
createDocumentFolder,
|
||||
@@ -79,6 +77,8 @@ type UploadItem = {
|
||||
error?: string
|
||||
}
|
||||
|
||||
type PersistedDocumentDraft = Pick<DocumentTab, 'key' | 'documentId' | 'parentId' | 'name' | 'extension' | 'mimeType' | 'markdown' | 'revision' | 'status' | 'mode'>
|
||||
|
||||
export function ProjectDocuments({
|
||||
session,
|
||||
projectId,
|
||||
@@ -167,17 +167,20 @@ export function ProjectDocuments({
|
||||
void refreshTree().then(async (items) => {
|
||||
if (cancelled) return
|
||||
const stored = readStoredTabs(projectId)
|
||||
const drafts = readStoredDrafts(projectId)
|
||||
const draftByDocumentID = new Map(drafts.filter((draft) => draft.documentId).map((draft) => [draft.documentId!, draft]))
|
||||
const restored: DocumentTab[] = []
|
||||
for (const id of stored) {
|
||||
const item = items.find((document) => document.id === id && document.kind === 'file')
|
||||
if (!item) continue
|
||||
try {
|
||||
const detail = await getDocument(session, projectId, id)
|
||||
if (!cancelled) restored.push(tabFromDocument(detail))
|
||||
if (!cancelled) restored.push(restoreDocumentDraft(tabFromDocument(detail), draftByDocumentID.get(id)))
|
||||
} catch {
|
||||
// Missing tabs are discarded.
|
||||
}
|
||||
}
|
||||
restored.push(...drafts.filter((draft) => !draft.documentId))
|
||||
if (cancelled) return
|
||||
if (restored.length > 0) {
|
||||
setTabs(restored)
|
||||
@@ -201,6 +204,8 @@ export function ProjectDocuments({
|
||||
useEffect(() => {
|
||||
const ids = tabs.flatMap((tab) => tab.documentId ? [tab.documentId] : [])
|
||||
localStorage.setItem(storageKey(projectId), JSON.stringify(ids))
|
||||
const drafts = tabs.filter((tab) => tab.status === 'dirty' || tab.status === 'saving' || tab.status === 'error')
|
||||
localStorage.setItem(draftStorageKey(projectId), JSON.stringify(drafts))
|
||||
}, [projectId, tabs])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -689,10 +694,12 @@ function SmartPreview({ session, projectId, tab }: { session: ApiSession; projec
|
||||
if (isText(extension, tab.mimeType)) {
|
||||
setText(await blob.text())
|
||||
} else if (extension === '.docx') {
|
||||
const result = await mammoth.convertToHtml({ arrayBuffer: await blob.arrayBuffer() })
|
||||
const mammoth = await import('mammoth')
|
||||
const result = await mammoth.default.convertToHtml({ arrayBuffer: await blob.arrayBuffer() })
|
||||
if (active) setHtml(DOMPurify.sanitize(result.value))
|
||||
} else if (extension === '.xlsx') {
|
||||
const parsed = await readXlsxSheet(blob)
|
||||
const { readSheet } = await import('read-excel-file/browser')
|
||||
const parsed = await readSheet(blob)
|
||||
if (active) setRows(parsed as unknown[][])
|
||||
} else {
|
||||
const previewBlob = extension === '.svg'
|
||||
@@ -793,6 +800,10 @@ function storageKey(projectId: string) {
|
||||
return `senlin:documents:tabs:${projectId}`
|
||||
}
|
||||
|
||||
function draftStorageKey(projectId: string) {
|
||||
return `senlin:documents:drafts:${projectId}`
|
||||
}
|
||||
|
||||
function readStoredTabs(projectId: string): string[] {
|
||||
try {
|
||||
const value: unknown = JSON.parse(localStorage.getItem(storageKey(projectId)) ?? '[]')
|
||||
@@ -802,6 +813,32 @@ function readStoredTabs(projectId: string): string[] {
|
||||
}
|
||||
}
|
||||
|
||||
function readStoredDrafts(projectId: string): PersistedDocumentDraft[] {
|
||||
try {
|
||||
const value: unknown = JSON.parse(localStorage.getItem(draftStorageKey(projectId)) ?? '[]')
|
||||
if (!Array.isArray(value)) return []
|
||||
return value.filter(isPersistedDocumentDraft)
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function isPersistedDocumentDraft(value: unknown): value is PersistedDocumentDraft {
|
||||
if (!value || typeof value !== 'object') return false
|
||||
const draft = value as Partial<PersistedDocumentDraft>
|
||||
return typeof draft.key === 'string' && typeof draft.name === 'string' && typeof draft.extension === 'string' &&
|
||||
typeof draft.mimeType === 'string' && typeof draft.markdown === 'string' && typeof draft.revision === 'number' &&
|
||||
(draft.status === 'dirty' || draft.status === 'saving' || draft.status === 'error') &&
|
||||
(draft.mode === 'edit' || draft.mode === 'preview') &&
|
||||
(draft.documentId === undefined || typeof draft.documentId === 'string') &&
|
||||
(draft.parentId === undefined || typeof draft.parentId === 'string')
|
||||
}
|
||||
|
||||
function restoreDocumentDraft(tab: DocumentTab, draft?: PersistedDocumentDraft): DocumentTab {
|
||||
if (!draft) return tab
|
||||
return { ...tab, markdown: draft.markdown, revision: draft.revision, status: draft.status === 'saving' ? 'dirty' : draft.status, mode: draft.mode }
|
||||
}
|
||||
|
||||
function saveLabel(status: SaveStatus) {
|
||||
return { clean: '已保存', dirty: '未保存', saving: '保存中', saved: '已保存', error: '保存失败' }[status]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user