@puredesktop/platform-editor 1.0.0-beta.1 → 1.0.0-beta.2

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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/src/CollapsePanel.tsx +35 -35
  3. package/src/DocumentDiffView.tsx +130 -130
  4. package/src/DocumentEditor.test.ts +118 -118
  5. package/src/DocumentEditor.tsx +2651 -2631
  6. package/src/alignedLineDiff.test.ts +52 -52
  7. package/src/alignedLineDiff.ts +67 -67
  8. package/src/collectionAssetSrc.ts +2 -2
  9. package/src/commentUtils.test.ts +350 -350
  10. package/src/commentUtils.ts +546 -546
  11. package/src/constants/toolKeys.ts +14 -14
  12. package/src/contentFormat.test.ts +27 -27
  13. package/src/contentFormat.ts +18 -18
  14. package/src/editorExtensions.ts +155 -155
  15. package/src/extensions/appliedChangeMark.ts +115 -115
  16. package/src/extensions/autoReviewPrompts.test.ts +529 -529
  17. package/src/extensions/autoReviewPrompts.ts +1442 -1442
  18. package/src/extensions/collectionImage.ts +26 -26
  19. package/src/extensions/collectionImagePaste.ts +215 -215
  20. package/src/extensions/commentMark.ts +223 -223
  21. package/src/extensions/documentAssetIdentity.ts +54 -54
  22. package/src/extensions/figure.ts +92 -92
  23. package/src/extensions/footnote.ts +186 -186
  24. package/src/extensions/indexMarker.ts +88 -88
  25. package/src/extensions/mathEditing.ts +239 -239
  26. package/src/extensions/mermaidBlock.tsx +297 -297
  27. package/src/extensions/slashCommands.test.ts +170 -170
  28. package/src/extensions/slashCommands.ts +746 -746
  29. package/src/extensions/smartTypography.test.ts +74 -74
  30. package/src/extensions/smartTypography.ts +120 -120
  31. package/src/index.ts +138 -137
  32. package/src/insertCollectionImage.test.ts +60 -60
  33. package/src/insertCollectionImage.ts +63 -63
  34. package/src/insertInlineAssetKind.test.ts +67 -67
  35. package/src/insertInlineAssetKind.ts +49 -49
  36. package/src/mermaidPreview.test.ts +54 -54
  37. package/src/mermaidPreview.ts +115 -115
  38. package/src/styled.ts +697 -676
  39. package/src/toolbar/ToolBtn.tsx +77 -63
  40. package/src/toolbar/Toolbar.test.tsx +325 -325
  41. package/src/toolbar/Toolbar.tsx +662 -624
  42. package/src/toolbar/groups/HeadingGroup.tsx +175 -153
  43. package/src/toolbar/overlayTypes.ts +28 -28
  44. package/src/useEditorExtensions.ts +22 -22
  45. package/src/utils/markdownUtils.ts +331 -331
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@puredesktop/platform-editor",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "TipTap editor package for PureScience plugin apps",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,35 +1,35 @@
1
- import { type ReactNode } from 'react'
2
- import { styled } from 'styled-components'
3
-
4
- export interface CollapsePanelProps {
5
- open: boolean
6
- children: ReactNode
7
- className?: string
8
- }
9
-
10
- export function CollapsePanel({
11
- open,
12
- children,
13
- className,
14
- }: CollapsePanelProps): React.ReactElement {
15
- return (
16
- <StyledPanel data-open={open} className={className}>
17
- <StyledInner>{children}</StyledInner>
18
- </StyledPanel>
19
- )
20
- }
21
-
22
- const StyledPanel = styled.div`
23
- display: grid;
24
- grid-template-rows: 0fr;
25
- transition: grid-template-rows 0.2s ease;
26
- overflow: hidden;
27
-
28
- &[data-open='true'] {
29
- grid-template-rows: 1fr;
30
- }
31
- `
32
-
33
- const StyledInner = styled.div`
34
- min-height: 0;
35
- `
1
+ import { type ReactNode } from 'react'
2
+ import { styled } from 'styled-components'
3
+
4
+ export interface CollapsePanelProps {
5
+ open: boolean
6
+ children: ReactNode
7
+ className?: string
8
+ }
9
+
10
+ export function CollapsePanel({
11
+ open,
12
+ children,
13
+ className,
14
+ }: CollapsePanelProps): React.ReactElement {
15
+ return (
16
+ <StyledPanel data-open={open} className={className}>
17
+ <StyledInner>{children}</StyledInner>
18
+ </StyledPanel>
19
+ )
20
+ }
21
+
22
+ const StyledPanel = styled.div`
23
+ display: grid;
24
+ grid-template-rows: 0fr;
25
+ transition: grid-template-rows 0.2s ease;
26
+ overflow: hidden;
27
+
28
+ &[data-open='true'] {
29
+ grid-template-rows: 1fr;
30
+ }
31
+ `
32
+
33
+ const StyledInner = styled.div`
34
+ min-height: 0;
35
+ `
@@ -1,130 +1,130 @@
1
- import { useMemo, type ReactElement } from 'react'
2
- import { styled } from 'styled-components'
3
- import {
4
- buildAlignedLineDiff,
5
- type AlignedDiffLine,
6
- type AlignedDiffLineKind,
7
- } from './alignedLineDiff.js'
8
-
9
- export interface DocumentDiffViewProps {
10
- left: string
11
- right: string
12
- leftLabel?: string
13
- rightLabel?: string
14
- }
15
-
16
- export function DocumentDiffView({
17
- left,
18
- right,
19
- leftLabel = 'Yours',
20
- rightLabel = 'On disk',
21
- }: DocumentDiffViewProps): ReactElement {
22
- const { leftLines, rightLines } = useMemo(
23
- () => buildAlignedLineDiff(left, right),
24
- [left, right],
25
- )
26
-
27
- return (
28
- <StyledRoot>
29
- <StyledHeader>
30
- <StyledColumnHeader $tone="left">{leftLabel}</StyledColumnHeader>
31
- <StyledColumnHeader $tone="right">{rightLabel}</StyledColumnHeader>
32
- </StyledHeader>
33
- <StyledGrid>
34
- <StyledColumn>
35
- {leftLines.map((line, index) => (
36
- <DiffLineRow key={`left-${index}`} line={line} />
37
- ))}
38
- </StyledColumn>
39
- <StyledColumn>
40
- {rightLines.map((line, index) => (
41
- <DiffLineRow key={`right-${index}`} line={line} />
42
- ))}
43
- </StyledColumn>
44
- </StyledGrid>
45
- </StyledRoot>
46
- )
47
- }
48
-
49
- function DiffLineRow({ line }: { line: AlignedDiffLine }): ReactElement {
50
- return (
51
- <StyledLine $kind={line.kind}>
52
- <StyledLineNo>{line.lineNumber ?? ''}</StyledLineNo>
53
- <StyledLineText>{line.text || '\u00a0'}</StyledLineText>
54
- </StyledLine>
55
- )
56
- }
57
-
58
- //#region styled-components
59
-
60
- const StyledRoot = styled.div`
61
- display: flex;
62
- flex-direction: column;
63
- min-height: 0;
64
- `
65
-
66
- const StyledHeader = styled.div`
67
- display: grid;
68
- grid-template-columns: 1fr 1fr;
69
- gap: var(--platform-spacing-sm);
70
- margin-bottom: var(--platform-spacing-sm);
71
- `
72
-
73
- const StyledColumnHeader = styled.div<{ $tone: 'left' | 'right' }>`
74
- padding: var(--platform-spacing-xs) var(--platform-spacing-sm);
75
- font-size: var(--platform-typography-font-size-sm);
76
- font-weight: var(--platform-typography-font-weight-medium);
77
- color: ${({ $tone }) =>
78
- $tone === 'left'
79
- ? 'var(--platform-colors-accent)'
80
- : 'var(--platform-colors-text-secondary)'};
81
- `
82
-
83
- const StyledGrid = styled.div`
84
- display: grid;
85
- grid-template-columns: 1fr 1fr;
86
- gap: var(--platform-spacing-sm);
87
- max-height: 60vh;
88
- overflow: auto;
89
- border: 1px solid var(--platform-colors-border);
90
- border-radius: 0;
91
- background: var(--platform-colors-bg);
92
- `
93
-
94
- const StyledColumn = styled.div`
95
- font-family: var(--platform-typography-font-family-mono);
96
- font-size: 12px;
97
- line-height: 1.45;
98
- padding: var(--platform-spacing-xs) 0;
99
- `
100
-
101
- const StyledLine = styled.div<{ $kind: AlignedDiffLineKind }>`
102
- display: grid;
103
- grid-template-columns: 36px 1fr;
104
- background: ${({ $kind }) =>
105
- $kind === 'added'
106
- ? 'rgb(from var(--platform-colors-success) r g b / 14%)'
107
- : $kind === 'removed'
108
- ? 'rgb(from var(--platform-colors-danger) r g b / 14%)'
109
- : 'transparent'};
110
- color: ${({ $kind }) =>
111
- $kind === 'pad'
112
- ? 'var(--platform-colors-text-disabled)'
113
- : 'var(--platform-colors-text)'};
114
- min-height: 17px;
115
- `
116
-
117
- const StyledLineNo = styled.div`
118
- color: var(--platform-colors-text-disabled);
119
- text-align: right;
120
- padding-right: 8px;
121
- user-select: none;
122
- `
123
-
124
- const StyledLineText = styled.div`
125
- white-space: pre-wrap;
126
- word-break: break-word;
127
- padding-right: var(--platform-spacing-sm);
128
- `
129
-
130
- //#endregion
1
+ import { useMemo, type ReactElement } from 'react'
2
+ import { styled } from 'styled-components'
3
+ import {
4
+ buildAlignedLineDiff,
5
+ type AlignedDiffLine,
6
+ type AlignedDiffLineKind,
7
+ } from './alignedLineDiff.js'
8
+
9
+ export interface DocumentDiffViewProps {
10
+ left: string
11
+ right: string
12
+ leftLabel?: string
13
+ rightLabel?: string
14
+ }
15
+
16
+ export function DocumentDiffView({
17
+ left,
18
+ right,
19
+ leftLabel = 'Yours',
20
+ rightLabel = 'On disk',
21
+ }: DocumentDiffViewProps): ReactElement {
22
+ const { leftLines, rightLines } = useMemo(
23
+ () => buildAlignedLineDiff(left, right),
24
+ [left, right],
25
+ )
26
+
27
+ return (
28
+ <StyledRoot>
29
+ <StyledHeader>
30
+ <StyledColumnHeader $tone="left">{leftLabel}</StyledColumnHeader>
31
+ <StyledColumnHeader $tone="right">{rightLabel}</StyledColumnHeader>
32
+ </StyledHeader>
33
+ <StyledGrid>
34
+ <StyledColumn>
35
+ {leftLines.map((line, index) => (
36
+ <DiffLineRow key={`left-${index}`} line={line} />
37
+ ))}
38
+ </StyledColumn>
39
+ <StyledColumn>
40
+ {rightLines.map((line, index) => (
41
+ <DiffLineRow key={`right-${index}`} line={line} />
42
+ ))}
43
+ </StyledColumn>
44
+ </StyledGrid>
45
+ </StyledRoot>
46
+ )
47
+ }
48
+
49
+ function DiffLineRow({ line }: { line: AlignedDiffLine }): ReactElement {
50
+ return (
51
+ <StyledLine $kind={line.kind}>
52
+ <StyledLineNo>{line.lineNumber ?? ''}</StyledLineNo>
53
+ <StyledLineText>{line.text || '\u00a0'}</StyledLineText>
54
+ </StyledLine>
55
+ )
56
+ }
57
+
58
+ //#region styled-components
59
+
60
+ const StyledRoot = styled.div`
61
+ display: flex;
62
+ flex-direction: column;
63
+ min-height: 0;
64
+ `
65
+
66
+ const StyledHeader = styled.div`
67
+ display: grid;
68
+ grid-template-columns: 1fr 1fr;
69
+ gap: var(--platform-spacing-sm);
70
+ margin-bottom: var(--platform-spacing-sm);
71
+ `
72
+
73
+ const StyledColumnHeader = styled.div<{ $tone: 'left' | 'right' }>`
74
+ padding: var(--platform-spacing-xs) var(--platform-spacing-sm);
75
+ font-size: var(--platform-typography-font-size-sm);
76
+ font-weight: var(--platform-typography-font-weight-medium);
77
+ color: ${({ $tone }) =>
78
+ $tone === 'left'
79
+ ? 'var(--platform-colors-accent)'
80
+ : 'var(--platform-colors-text-secondary)'};
81
+ `
82
+
83
+ const StyledGrid = styled.div`
84
+ display: grid;
85
+ grid-template-columns: 1fr 1fr;
86
+ gap: var(--platform-spacing-sm);
87
+ max-height: 60vh;
88
+ overflow: auto;
89
+ border: 1px solid var(--platform-colors-border);
90
+ border-radius: 0;
91
+ background: var(--platform-colors-bg);
92
+ `
93
+
94
+ const StyledColumn = styled.div`
95
+ font-family: var(--platform-typography-font-family-mono);
96
+ font-size: 12px;
97
+ line-height: 1.45;
98
+ padding: var(--platform-spacing-xs) 0;
99
+ `
100
+
101
+ const StyledLine = styled.div<{ $kind: AlignedDiffLineKind }>`
102
+ display: grid;
103
+ grid-template-columns: 36px 1fr;
104
+ background: ${({ $kind }) =>
105
+ $kind === 'added'
106
+ ? 'rgb(from var(--platform-colors-success) r g b / 14%)'
107
+ : $kind === 'removed'
108
+ ? 'rgb(from var(--platform-colors-danger) r g b / 14%)'
109
+ : 'transparent'};
110
+ color: ${({ $kind }) =>
111
+ $kind === 'pad'
112
+ ? 'var(--platform-colors-text-disabled)'
113
+ : 'var(--platform-colors-text)'};
114
+ min-height: 17px;
115
+ `
116
+
117
+ const StyledLineNo = styled.div`
118
+ color: var(--platform-colors-text-disabled);
119
+ text-align: right;
120
+ padding-right: 8px;
121
+ user-select: none;
122
+ `
123
+
124
+ const StyledLineText = styled.div`
125
+ white-space: pre-wrap;
126
+ word-break: break-word;
127
+ padding-right: var(--platform-spacing-sm);
128
+ `
129
+
130
+ //#endregion
@@ -1,118 +1,118 @@
1
- // @vitest-environment happy-dom
2
- import type { Editor } from '@tiptap/core'
3
- import { describe, expect, it, vi } from 'vitest'
4
- import {
5
- isReviewRailCommentType,
6
- isSelectionAnnotatable,
7
- progressiveAutoReviewBatches,
8
- } from './DocumentEditor.js'
9
-
10
- function mockEditorForNodes(
11
- startNode: Node,
12
- endNode: Node,
13
- options: { editable?: boolean; destroyed?: boolean } = {},
14
- ): Editor {
15
- return {
16
- isDestroyed: options.destroyed ?? false,
17
- isEditable: options.editable ?? true,
18
- view: {
19
- domAtPos: vi.fn((pos: number) => ({
20
- node: pos <= 1 ? startNode : endNode,
21
- })),
22
- },
23
- } as unknown as Editor
24
- }
25
-
26
- describe('DocumentEditor annotation selection rules', () => {
27
- it('shows rail badges for editorial review objects', () => {
28
- expect(isReviewRailCommentType('copyedit')).toBe(true)
29
- expect(isReviewRailCommentType('voice')).toBe(true)
30
- expect(isReviewRailCommentType('claim')).toBe(true)
31
- expect(isReviewRailCommentType('fact-check')).toBe(true)
32
- expect(isReviewRailCommentType('general')).toBe(false)
33
- })
34
-
35
- it('allows non-empty selections in editable manuscript content', () => {
36
- const paragraph = document.createElement('p')
37
- const start = document.createTextNode('Selected')
38
- const end = document.createTextNode(' text')
39
- paragraph.append(start, end)
40
- const editor = mockEditorForNodes(start, end)
41
-
42
- expect(isSelectionAnnotatable(editor, 1, 4)).toBe(true)
43
- })
44
-
45
- it('does not offer annotation creation inside protected generated content', () => {
46
- const referenceEntry = document.createElement('li')
47
- referenceEntry.setAttribute('data-pm-reference-entry', 'true')
48
- referenceEntry.setAttribute('contenteditable', 'false')
49
- const start = document.createTextNode('Generated')
50
- const end = document.createTextNode(' reference')
51
- referenceEntry.append(start, end)
52
- const editor = mockEditorForNodes(start, end)
53
-
54
- expect(isSelectionAnnotatable(editor, 1, 4)).toBe(false)
55
- })
56
-
57
- it('does not offer annotation creation inside citation atoms', () => {
58
- const citation = document.createElement('cite')
59
- citation.className = 'ps-citation'
60
- const start = document.createTextNode('[1')
61
- const end = document.createTextNode(']')
62
- citation.append(start, end)
63
- const editor = mockEditorForNodes(start, end)
64
-
65
- expect(isSelectionAnnotatable(editor, 1, 4)).toBe(false)
66
- })
67
-
68
- it('does not offer annotation creation for collapsed or read-only selections', () => {
69
- const text = document.createTextNode('Text')
70
-
71
- expect(isSelectionAnnotatable(mockEditorForNodes(text, text), 2, 2)).toBe(
72
- false,
73
- )
74
- expect(
75
- isSelectionAnnotatable(
76
- mockEditorForNodes(text, text, { editable: false }),
77
- 1,
78
- 4,
79
- ),
80
- ).toBe(false)
81
- })
82
- })
83
-
84
- describe('DocumentEditor progressive editorial review', () => {
85
- it('front-loads small batches before larger background batches', () => {
86
- const paragraphs = Array.from({ length: 31 }, (_, index) => ({
87
- id: `p-${index + 1}`,
88
- text: `Paragraph ${index + 1}`,
89
- from: index + 1,
90
- pos: index + 1,
91
- to: index + 2,
92
- }))
93
-
94
- const batches = progressiveAutoReviewBatches(paragraphs)
95
-
96
- expect(batches.map(batch => batch.length)).toEqual([3, 3, 3, 3, 12, 7])
97
- expect(batches[0]?.map(paragraph => paragraph.id)).toEqual([
98
- 'p-1',
99
- 'p-2',
100
- 'p-3',
101
- ])
102
- expect(batches[4]?.[0]?.id).toBe('p-13')
103
- })
104
-
105
- it('keeps short documents in small startup batches only', () => {
106
- const paragraphs = Array.from({ length: 5 }, (_, index) => ({
107
- id: `p-${index + 1}`,
108
- text: `Paragraph ${index + 1}`,
109
- from: index + 1,
110
- pos: index + 1,
111
- to: index + 2,
112
- }))
113
-
114
- expect(
115
- progressiveAutoReviewBatches(paragraphs).map(batch => batch.length),
116
- ).toEqual([3, 2])
117
- })
118
- })
1
+ // @vitest-environment happy-dom
2
+ import type { Editor } from '@tiptap/core'
3
+ import { describe, expect, it, vi } from 'vitest'
4
+ import {
5
+ isReviewRailCommentType,
6
+ isSelectionAnnotatable,
7
+ progressiveAutoReviewBatches,
8
+ } from './DocumentEditor.js'
9
+
10
+ function mockEditorForNodes(
11
+ startNode: Node,
12
+ endNode: Node,
13
+ options: { editable?: boolean; destroyed?: boolean } = {},
14
+ ): Editor {
15
+ return {
16
+ isDestroyed: options.destroyed ?? false,
17
+ isEditable: options.editable ?? true,
18
+ view: {
19
+ domAtPos: vi.fn((pos: number) => ({
20
+ node: pos <= 1 ? startNode : endNode,
21
+ })),
22
+ },
23
+ } as unknown as Editor
24
+ }
25
+
26
+ describe('DocumentEditor annotation selection rules', () => {
27
+ it('shows rail badges for editorial review objects', () => {
28
+ expect(isReviewRailCommentType('copyedit')).toBe(true)
29
+ expect(isReviewRailCommentType('voice')).toBe(true)
30
+ expect(isReviewRailCommentType('claim')).toBe(true)
31
+ expect(isReviewRailCommentType('fact-check')).toBe(true)
32
+ expect(isReviewRailCommentType('general')).toBe(false)
33
+ })
34
+
35
+ it('allows non-empty selections in editable manuscript content', () => {
36
+ const paragraph = document.createElement('p')
37
+ const start = document.createTextNode('Selected')
38
+ const end = document.createTextNode(' text')
39
+ paragraph.append(start, end)
40
+ const editor = mockEditorForNodes(start, end)
41
+
42
+ expect(isSelectionAnnotatable(editor, 1, 4)).toBe(true)
43
+ })
44
+
45
+ it('does not offer annotation creation inside protected generated content', () => {
46
+ const referenceEntry = document.createElement('li')
47
+ referenceEntry.setAttribute('data-pm-reference-entry', 'true')
48
+ referenceEntry.setAttribute('contenteditable', 'false')
49
+ const start = document.createTextNode('Generated')
50
+ const end = document.createTextNode(' reference')
51
+ referenceEntry.append(start, end)
52
+ const editor = mockEditorForNodes(start, end)
53
+
54
+ expect(isSelectionAnnotatable(editor, 1, 4)).toBe(false)
55
+ })
56
+
57
+ it('does not offer annotation creation inside citation atoms', () => {
58
+ const citation = document.createElement('cite')
59
+ citation.className = 'ps-citation'
60
+ const start = document.createTextNode('[1')
61
+ const end = document.createTextNode(']')
62
+ citation.append(start, end)
63
+ const editor = mockEditorForNodes(start, end)
64
+
65
+ expect(isSelectionAnnotatable(editor, 1, 4)).toBe(false)
66
+ })
67
+
68
+ it('does not offer annotation creation for collapsed or read-only selections', () => {
69
+ const text = document.createTextNode('Text')
70
+
71
+ expect(isSelectionAnnotatable(mockEditorForNodes(text, text), 2, 2)).toBe(
72
+ false,
73
+ )
74
+ expect(
75
+ isSelectionAnnotatable(
76
+ mockEditorForNodes(text, text, { editable: false }),
77
+ 1,
78
+ 4,
79
+ ),
80
+ ).toBe(false)
81
+ })
82
+ })
83
+
84
+ describe('DocumentEditor progressive editorial review', () => {
85
+ it('front-loads small batches before larger background batches', () => {
86
+ const paragraphs = Array.from({ length: 31 }, (_, index) => ({
87
+ id: `p-${index + 1}`,
88
+ text: `Paragraph ${index + 1}`,
89
+ from: index + 1,
90
+ pos: index + 1,
91
+ to: index + 2,
92
+ }))
93
+
94
+ const batches = progressiveAutoReviewBatches(paragraphs)
95
+
96
+ expect(batches.map(batch => batch.length)).toEqual([3, 3, 3, 3, 12, 7])
97
+ expect(batches[0]?.map(paragraph => paragraph.id)).toEqual([
98
+ 'p-1',
99
+ 'p-2',
100
+ 'p-3',
101
+ ])
102
+ expect(batches[4]?.[0]?.id).toBe('p-13')
103
+ })
104
+
105
+ it('keeps short documents in small startup batches only', () => {
106
+ const paragraphs = Array.from({ length: 5 }, (_, index) => ({
107
+ id: `p-${index + 1}`,
108
+ text: `Paragraph ${index + 1}`,
109
+ from: index + 1,
110
+ pos: index + 1,
111
+ to: index + 2,
112
+ }))
113
+
114
+ expect(
115
+ progressiveAutoReviewBatches(paragraphs).map(batch => batch.length),
116
+ ).toEqual([3, 2])
117
+ })
118
+ })