@tessera-editor/core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.d.ts +903 -0
- package/dist/index.js +2538 -0
- package/dist/index.js.map +1 -0
- package/dist/tessera.css +1491 -0
- package/package.json +72 -0
- package/src/__tests__/markdown.test.ts +138 -0
- package/src/__tests__/v11.test.ts +184 -0
- package/src/__tests__/v112.test.ts +184 -0
- package/src/__tests__/writeback.test.ts +123 -0
- package/src/blockmenu.ts +38 -0
- package/src/diff.ts +150 -0
- package/src/extensions/context-menu.ts +55 -0
- package/src/extensions/emoji.ts +148 -0
- package/src/extensions/find-replace.ts +229 -0
- package/src/extensions/gallery.ts +111 -0
- package/src/extensions/history.ts +133 -0
- package/src/extensions/input-rules.ts +34 -0
- package/src/extensions/metrics.ts +61 -0
- package/src/extensions/shortcuts.ts +118 -0
- package/src/extensions/slash.ts +272 -0
- package/src/extensions/word-paste.ts +25 -0
- package/src/i18n.ts +320 -0
- package/src/index.ts +80 -0
- package/src/markdown.ts +260 -0
- package/src/marks/ai.ts +55 -0
- package/src/marks/comment.ts +137 -0
- package/src/marks/placeholder.ts +63 -0
- package/src/nodes/collapsible.ts +171 -0
- package/src/nodes/embed.ts +55 -0
- package/src/nodes/hint.ts +70 -0
- package/src/nodes/image.ts +77 -0
- package/src/nodes/table.ts +286 -0
- package/src/nodes/toc.ts +38 -0
- package/src/preset.ts +117 -0
- package/src/services.ts +103 -0
- package/src/styles/tessera.css +1491 -0
- package/src/wordpaste.ts +56 -0
- package/src/writeback.ts +156 -0
package/src/preset.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { Extensions } from '@tiptap/core'
|
|
2
|
+
import StarterKit from '@tiptap/starter-kit'
|
|
3
|
+
import { TaskList, TaskItem } from '@tiptap/extension-list'
|
|
4
|
+
import { TextStyle } from '@tiptap/extension-text-style'
|
|
5
|
+
import { Color } from '@tiptap/extension-color'
|
|
6
|
+
import { Highlight } from '@tiptap/extension-highlight'
|
|
7
|
+
import { UniqueID } from '@tiptap/extension-unique-id'
|
|
8
|
+
import { Placeholder } from '@tiptap/extensions'
|
|
9
|
+
import { Hint } from './nodes/hint'
|
|
10
|
+
import { Collapsible, CollapsibleSummary, CollapsibleContent } from './nodes/collapsible'
|
|
11
|
+
import { ImageBlock } from './nodes/image'
|
|
12
|
+
import { AiTable, AiTableRow, AiTableCell, AiTableHeader } from './nodes/table'
|
|
13
|
+
import { EmbedBlock } from './nodes/embed'
|
|
14
|
+
import { TocBlock } from './nodes/toc'
|
|
15
|
+
import { AiAttribution } from './marks/ai'
|
|
16
|
+
import { CommentMark, CommentCommands } from './marks/comment'
|
|
17
|
+
import { PlaceholderMark, PlaceholderCommands } from './marks/placeholder'
|
|
18
|
+
import { TesseraInputRules } from './extensions/input-rules'
|
|
19
|
+
import { TesseraShortcuts } from './extensions/shortcuts'
|
|
20
|
+
import { TesseraFindReplace } from './extensions/find-replace'
|
|
21
|
+
import { SlashMenu } from './extensions/slash'
|
|
22
|
+
import { EmojiMenu } from './extensions/emoji'
|
|
23
|
+
import { TesseraHistory } from './extensions/history'
|
|
24
|
+
import { BlockContextMenu } from './extensions/context-menu'
|
|
25
|
+
import { TesseraGallery } from './extensions/gallery'
|
|
26
|
+
import { TesseraMetrics } from './extensions/metrics'
|
|
27
|
+
import { TesseraWordPaste } from './extensions/word-paste'
|
|
28
|
+
import { TesseraServices } from './services'
|
|
29
|
+
import { createTesseraT, type TesseraLocale } from './i18n'
|
|
30
|
+
|
|
31
|
+
export interface TesseraPresetOptions {
|
|
32
|
+
locale?: TesseraLocale
|
|
33
|
+
/** v1.1 history auto-capture idle window (playground uses a short one) */
|
|
34
|
+
historyIdleMs?: number
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Node types that receive stable block IDs (write-back protocol basis). */
|
|
38
|
+
export const ID_BLOCK_TYPES = [
|
|
39
|
+
'paragraph',
|
|
40
|
+
'heading',
|
|
41
|
+
'bulletList',
|
|
42
|
+
'orderedList',
|
|
43
|
+
'taskList',
|
|
44
|
+
'listItem',
|
|
45
|
+
'taskItem',
|
|
46
|
+
'blockquote',
|
|
47
|
+
'codeBlock',
|
|
48
|
+
'horizontalRule',
|
|
49
|
+
'hint',
|
|
50
|
+
'collapsible',
|
|
51
|
+
'imageBlock',
|
|
52
|
+
'table',
|
|
53
|
+
'tableRow',
|
|
54
|
+
'embedBlock',
|
|
55
|
+
'tocBlock',
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The Tessera extension preset: framework-agnostic, UI-free. The binding layers
|
|
60
|
+
* rendering (slash menu renderer, node views) on top of this.
|
|
61
|
+
*/
|
|
62
|
+
export function createTesseraExtensions(options: TesseraPresetOptions = {}): Extensions {
|
|
63
|
+
const t = createTesseraT(options.locale ?? 'zh-CN')
|
|
64
|
+
|
|
65
|
+
return [
|
|
66
|
+
StarterKit.configure({
|
|
67
|
+
heading: { levels: [1, 2, 3, 4] },
|
|
68
|
+
link: {
|
|
69
|
+
openOnClick: false,
|
|
70
|
+
autolink: true,
|
|
71
|
+
defaultProtocol: 'https',
|
|
72
|
+
},
|
|
73
|
+
// undoRedo keeps defaults (newGroupDelay 500ms): streaming AI chunks
|
|
74
|
+
// arriving faster than that already merge into one undo step.
|
|
75
|
+
}),
|
|
76
|
+
TextStyle,
|
|
77
|
+
Color,
|
|
78
|
+
Highlight.configure({ multicolor: true }),
|
|
79
|
+
TaskList,
|
|
80
|
+
TaskItem.configure({ nested: true }),
|
|
81
|
+
Hint,
|
|
82
|
+
Collapsible,
|
|
83
|
+
CollapsibleSummary,
|
|
84
|
+
CollapsibleContent,
|
|
85
|
+
ImageBlock,
|
|
86
|
+
AiTable,
|
|
87
|
+
AiTableRow,
|
|
88
|
+
AiTableCell,
|
|
89
|
+
AiTableHeader,
|
|
90
|
+
EmbedBlock,
|
|
91
|
+
TocBlock,
|
|
92
|
+
AiAttribution,
|
|
93
|
+
CommentMark,
|
|
94
|
+
CommentCommands,
|
|
95
|
+
PlaceholderMark,
|
|
96
|
+
PlaceholderCommands,
|
|
97
|
+
UniqueID.configure({
|
|
98
|
+
types: ID_BLOCK_TYPES,
|
|
99
|
+
attributeName: 'id',
|
|
100
|
+
}),
|
|
101
|
+
Placeholder.configure({
|
|
102
|
+
placeholder: ({ node }) => (node.type.name === 'paragraph' ? t('placeholderEmpty') : ''),
|
|
103
|
+
showOnlyWhenEditable: true,
|
|
104
|
+
}),
|
|
105
|
+
TesseraInputRules,
|
|
106
|
+
TesseraShortcuts,
|
|
107
|
+
TesseraFindReplace,
|
|
108
|
+
TesseraHistory.configure({ idleMs: options.historyIdleMs }),
|
|
109
|
+
BlockContextMenu,
|
|
110
|
+
SlashMenu.configure({ locale: options.locale ?? 'zh-CN' }),
|
|
111
|
+
EmojiMenu,
|
|
112
|
+
TesseraGallery,
|
|
113
|
+
TesseraMetrics,
|
|
114
|
+
TesseraWordPaste,
|
|
115
|
+
TesseraServices,
|
|
116
|
+
]
|
|
117
|
+
}
|
package/src/services.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { Extension } from '@tiptap/core'
|
|
2
|
+
import type { JSONContent } from '@tiptap/core'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Injected services (ADR-0001 family): the component family never performs
|
|
6
|
+
* network or storage I/O itself. Hosts provide implementations through the
|
|
7
|
+
* binding; the editor reads them from `editor.storage.tesseraServices`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export interface UploadedAsset {
|
|
11
|
+
url: string
|
|
12
|
+
name?: string
|
|
13
|
+
mime?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface UploadService {
|
|
17
|
+
uploadImage(file: File | Blob): Promise<UploadedAsset>
|
|
18
|
+
uploadFile?(file: File | Blob): Promise<UploadedAsset>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** v1.1: persistent version history storage (snapshots keyed by time). */
|
|
22
|
+
export interface DocSnapshot {
|
|
23
|
+
id: string
|
|
24
|
+
ts: number
|
|
25
|
+
doc: JSONContent
|
|
26
|
+
label?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface StorageService {
|
|
30
|
+
saveSnapshot(snapshot: DocSnapshot): Promise<void>
|
|
31
|
+
listSnapshots(): Promise<DocSnapshot[]>
|
|
32
|
+
deleteSnapshot?(id: string): Promise<void>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** v1.1: inline comments. */
|
|
36
|
+
export interface CommentEntry {
|
|
37
|
+
id: string
|
|
38
|
+
authorId: string
|
|
39
|
+
authorName: string
|
|
40
|
+
text: string
|
|
41
|
+
ts: number
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface CommentThread {
|
|
45
|
+
id: string
|
|
46
|
+
quote: string
|
|
47
|
+
resolved: boolean
|
|
48
|
+
createdAt: number
|
|
49
|
+
entries: CommentEntry[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface CommentStore {
|
|
53
|
+
list(): Promise<CommentThread[]>
|
|
54
|
+
upsert(thread: CommentThread): Promise<void>
|
|
55
|
+
remove(id: string): Promise<void>
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** v1.1: who is editing (author of comments / history labels). */
|
|
59
|
+
export interface IdentityService {
|
|
60
|
+
getCurrentUser(): { id: string; name: string } | null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface TesseraServicesStorage {
|
|
64
|
+
upload?: UploadService
|
|
65
|
+
storage?: StorageService
|
|
66
|
+
comments?: CommentStore
|
|
67
|
+
identity?: IdentityService
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const TesseraServices = Extension.create({
|
|
71
|
+
name: 'tesseraServices',
|
|
72
|
+
|
|
73
|
+
addStorage() {
|
|
74
|
+
return {
|
|
75
|
+
upload: undefined,
|
|
76
|
+
storage: undefined,
|
|
77
|
+
comments: undefined,
|
|
78
|
+
identity: undefined,
|
|
79
|
+
} satisfies TesseraServicesStorage
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
type ServicesEditor = { storage: unknown }
|
|
84
|
+
|
|
85
|
+
function servicesBag(editor: ServicesEditor): TesseraServicesStorage | undefined {
|
|
86
|
+
return (editor.storage as Record<string, TesseraServicesStorage | undefined>).tesseraServices
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getUploadService(editor: ServicesEditor): UploadService | undefined {
|
|
90
|
+
return servicesBag(editor)?.upload
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function getStorageService(editor: ServicesEditor): StorageService | undefined {
|
|
94
|
+
return servicesBag(editor)?.storage
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function getCommentStore(editor: ServicesEditor): CommentStore | undefined {
|
|
98
|
+
return servicesBag(editor)?.comments
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getIdentityService(editor: ServicesEditor): IdentityService | undefined {
|
|
102
|
+
return servicesBag(editor)?.identity
|
|
103
|
+
}
|