@portabletext/editor 1.48.14 → 1.49.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/lib/_chunks-cjs/editor-provider.cjs +117 -34
- package/lib/_chunks-cjs/editor-provider.cjs.map +1 -1
- package/lib/_chunks-cjs/util.slice-blocks.cjs +2 -0
- package/lib/_chunks-cjs/util.slice-blocks.cjs.map +1 -1
- package/lib/_chunks-es/editor-provider.js +117 -34
- package/lib/_chunks-es/editor-provider.js.map +1 -1
- package/lib/_chunks-es/util.slice-blocks.js +2 -0
- package/lib/_chunks-es/util.slice-blocks.js.map +1 -1
- package/lib/behaviors/index.d.cts +222 -208
- package/lib/behaviors/index.d.ts +222 -208
- package/lib/index.cjs +346 -257
- package/lib/index.cjs.map +1 -1
- package/lib/index.d.cts +222 -216
- package/lib/index.d.ts +222 -216
- package/lib/index.js +353 -264
- package/lib/index.js.map +1 -1
- package/lib/plugins/index.d.cts +222 -216
- package/lib/plugins/index.d.ts +222 -216
- package/lib/selectors/index.d.cts +222 -208
- package/lib/selectors/index.d.ts +222 -208
- package/lib/utils/index.d.cts +222 -208
- package/lib/utils/index.d.ts +222 -208
- package/package.json +1 -1
- package/src/behaviors/behavior.config.ts +7 -0
- package/src/behaviors/behavior.core.block-element.ts +108 -0
- package/src/behaviors/behavior.core.ts +6 -2
- package/src/converters/converter.portable-text.ts +4 -1
- package/src/converters/converter.text-html.ts +4 -1
- package/src/converters/converter.text-plain.ts +4 -1
- package/src/editor/Editable.tsx +2 -4
- package/src/editor/__tests__/PortableTextEditor.test.tsx +6 -0
- package/src/editor/components/Leaf.tsx +8 -1
- package/src/editor/components/render-block-object.tsx +90 -0
- package/src/editor/components/render-default-object.tsx +21 -0
- package/src/editor/components/render-element.tsx +140 -0
- package/src/editor/components/render-inline-object.tsx +93 -0
- package/src/editor/components/render-text-block.tsx +148 -0
- package/src/editor/components/use-core-block-element-behaviors.ts +39 -0
- package/src/editor/create-editor.ts +17 -5
- package/src/editor/editor-machine.ts +21 -18
- package/src/internal-utils/parse-blocks.ts +2 -2
- package/src/internal-utils/slate-utils.ts +1 -1
- package/src/priority/priority.core.ts +3 -0
- package/src/priority/priority.sort.test.ts +319 -0
- package/src/priority/priority.sort.ts +121 -0
- package/src/priority/priority.types.ts +24 -0
- package/src/editor/components/DefaultObject.tsx +0 -21
- package/src/editor/components/Element.tsx +0 -435
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type {PortableTextTextBlock} from '@sanity/types'
|
|
2
|
+
import {useSelector} from '@xstate/react'
|
|
3
|
+
import {useContext, useRef, useState, type ReactElement} from 'react'
|
|
4
|
+
import {Range, type Element as SlateElement} from 'slate'
|
|
5
|
+
import {useSelected, useSlateStatic, type RenderElementProps} from 'slate-react'
|
|
6
|
+
import type {EventPositionBlock} from '../../internal-utils/event-position'
|
|
7
|
+
import type {
|
|
8
|
+
RenderBlockFunction,
|
|
9
|
+
RenderListItemFunction,
|
|
10
|
+
RenderStyleFunction,
|
|
11
|
+
} from '../../types/editor'
|
|
12
|
+
import {EditorActorContext} from '../editor-actor-context'
|
|
13
|
+
import {DropIndicator} from './drop-indicator'
|
|
14
|
+
import {useCoreBlockElementBehaviors} from './use-core-block-element-behaviors'
|
|
15
|
+
|
|
16
|
+
export function RenderTextBlock(props: {
|
|
17
|
+
attributes: RenderElementProps['attributes']
|
|
18
|
+
children: ReactElement
|
|
19
|
+
element: SlateElement
|
|
20
|
+
readOnly: boolean
|
|
21
|
+
renderBlock?: RenderBlockFunction
|
|
22
|
+
renderListItem?: RenderListItemFunction
|
|
23
|
+
renderStyle?: RenderStyleFunction
|
|
24
|
+
spellCheck?: boolean
|
|
25
|
+
textBlock: PortableTextTextBlock
|
|
26
|
+
}) {
|
|
27
|
+
const [dragPositionBlock, setDragPositionBlock] =
|
|
28
|
+
useState<EventPositionBlock>()
|
|
29
|
+
const blockRef = useRef<HTMLDivElement>(null)
|
|
30
|
+
|
|
31
|
+
const slateEditor = useSlateStatic()
|
|
32
|
+
const selected = useSelected()
|
|
33
|
+
|
|
34
|
+
const editorActor = useContext(EditorActorContext)
|
|
35
|
+
|
|
36
|
+
useCoreBlockElementBehaviors({
|
|
37
|
+
key: props.element._key,
|
|
38
|
+
onSetDragPositionBlock: setDragPositionBlock,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const legacySchema = useSelector(editorActor, (s) =>
|
|
42
|
+
s.context.getLegacySchema(),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const focused =
|
|
46
|
+
selected &&
|
|
47
|
+
slateEditor.selection !== null &&
|
|
48
|
+
Range.isCollapsed(slateEditor.selection)
|
|
49
|
+
|
|
50
|
+
let children = props.children
|
|
51
|
+
|
|
52
|
+
const legacyBlockSchemaType = legacySchema.block
|
|
53
|
+
|
|
54
|
+
if (props.renderStyle && props.textBlock.style) {
|
|
55
|
+
const legacyStyleSchemaType =
|
|
56
|
+
props.textBlock.style !== undefined
|
|
57
|
+
? legacySchema.styles.find(
|
|
58
|
+
(style) => style.value === props.textBlock.style,
|
|
59
|
+
)
|
|
60
|
+
: undefined
|
|
61
|
+
|
|
62
|
+
if (legacyStyleSchemaType) {
|
|
63
|
+
children = props.renderStyle({
|
|
64
|
+
block: props.textBlock,
|
|
65
|
+
children,
|
|
66
|
+
editorElementRef: blockRef,
|
|
67
|
+
focused,
|
|
68
|
+
path: [{_key: props.textBlock._key}],
|
|
69
|
+
schemaType: legacyStyleSchemaType,
|
|
70
|
+
selected,
|
|
71
|
+
value: props.textBlock.style,
|
|
72
|
+
})
|
|
73
|
+
} else {
|
|
74
|
+
console.error(
|
|
75
|
+
`Unable to find Schema type for text block style ${props.textBlock.style}`,
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (props.renderListItem && props.textBlock.listItem) {
|
|
81
|
+
const legacyListItemSchemaType = legacySchema.lists.find(
|
|
82
|
+
(list) => list.value === props.textBlock.listItem,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if (legacyListItemSchemaType) {
|
|
86
|
+
children = props.renderListItem({
|
|
87
|
+
block: props.textBlock,
|
|
88
|
+
children,
|
|
89
|
+
editorElementRef: blockRef,
|
|
90
|
+
focused,
|
|
91
|
+
level: props.textBlock.level ?? 1,
|
|
92
|
+
path: [{_key: props.textBlock._key}],
|
|
93
|
+
selected,
|
|
94
|
+
value: props.textBlock.listItem,
|
|
95
|
+
schemaType: legacyListItemSchemaType,
|
|
96
|
+
})
|
|
97
|
+
} else {
|
|
98
|
+
console.error(
|
|
99
|
+
`Unable to find Schema type for text block list item ${props.textBlock.listItem}`,
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
key={props.element._key}
|
|
107
|
+
{...props.attributes}
|
|
108
|
+
className={[
|
|
109
|
+
'pt-block',
|
|
110
|
+
'pt-text-block',
|
|
111
|
+
...(props.textBlock.style
|
|
112
|
+
? [`pt-text-block-style-${props.textBlock.style}`]
|
|
113
|
+
: []),
|
|
114
|
+
...(props.textBlock.listItem
|
|
115
|
+
? [
|
|
116
|
+
'pt-list-item',
|
|
117
|
+
`pt-list-item-${props.textBlock.listItem}`,
|
|
118
|
+
`pt-list-item-level-${props.textBlock.level ?? 1}`,
|
|
119
|
+
]
|
|
120
|
+
: []),
|
|
121
|
+
].join(' ')}
|
|
122
|
+
spellCheck={props.spellCheck}
|
|
123
|
+
data-block-key={props.textBlock._key}
|
|
124
|
+
data-block-name={props.textBlock._type}
|
|
125
|
+
data-block-type="text"
|
|
126
|
+
>
|
|
127
|
+
{dragPositionBlock === 'start' ? <DropIndicator /> : null}
|
|
128
|
+
<div ref={blockRef}>
|
|
129
|
+
{props.renderBlock
|
|
130
|
+
? props.renderBlock({
|
|
131
|
+
children,
|
|
132
|
+
editorElementRef: blockRef,
|
|
133
|
+
focused,
|
|
134
|
+
level: props.textBlock.level,
|
|
135
|
+
listItem: props.textBlock.listItem,
|
|
136
|
+
path: [{_key: props.textBlock._key}],
|
|
137
|
+
selected,
|
|
138
|
+
schemaType: legacyBlockSchemaType,
|
|
139
|
+
style: props.textBlock.style,
|
|
140
|
+
type: legacyBlockSchemaType,
|
|
141
|
+
value: props.textBlock,
|
|
142
|
+
})
|
|
143
|
+
: props.children}
|
|
144
|
+
</div>
|
|
145
|
+
{dragPositionBlock === 'end' ? <DropIndicator /> : null}
|
|
146
|
+
</div>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import {useContext, useEffect} from 'react'
|
|
2
|
+
import {createCoreBlockElementBehaviorsConfig} from '../../behaviors/behavior.core.block-element'
|
|
3
|
+
import type {EventPositionBlock} from '../../internal-utils/event-position'
|
|
4
|
+
import {EditorActorContext} from '../editor-actor-context'
|
|
5
|
+
|
|
6
|
+
export function useCoreBlockElementBehaviors({
|
|
7
|
+
key,
|
|
8
|
+
onSetDragPositionBlock,
|
|
9
|
+
}: {
|
|
10
|
+
key: string
|
|
11
|
+
onSetDragPositionBlock: (
|
|
12
|
+
eventPositionBlock: EventPositionBlock | undefined,
|
|
13
|
+
) => void
|
|
14
|
+
}) {
|
|
15
|
+
const editorActor = useContext(EditorActorContext)
|
|
16
|
+
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
const behaviorConfigs = createCoreBlockElementBehaviorsConfig({
|
|
19
|
+
key,
|
|
20
|
+
onSetDragPositionBlock,
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
for (const behaviorConfig of behaviorConfigs) {
|
|
24
|
+
editorActor.send({
|
|
25
|
+
type: 'add behavior',
|
|
26
|
+
behaviorConfig,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return () => {
|
|
31
|
+
for (const behaviorConfig of behaviorConfigs) {
|
|
32
|
+
editorActor.send({
|
|
33
|
+
type: 'remove behavior',
|
|
34
|
+
behaviorConfig,
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}, [editorActor, key, onSetDragPositionBlock])
|
|
39
|
+
}
|
|
@@ -8,6 +8,8 @@ import type {Behavior} from '../behaviors/behavior.types.behavior'
|
|
|
8
8
|
import type {ExternalBehaviorEvent} from '../behaviors/behavior.types.event'
|
|
9
9
|
import {createCoreConverters} from '../converters/converters.core'
|
|
10
10
|
import {compileType} from '../internal-utils/schema'
|
|
11
|
+
import {corePriority} from '../priority/priority.core'
|
|
12
|
+
import {createEditorPriority} from '../priority/priority.types'
|
|
11
13
|
import type {EditableAPI} from '../types/editor'
|
|
12
14
|
import {createSlateEditor, type SlateEditor} from './create-slate-editor'
|
|
13
15
|
import type {
|
|
@@ -117,23 +119,33 @@ export function createInternalEditor(editorActor: EditorActor): InternalEditor {
|
|
|
117
119
|
editorActorSnapshot: editorActor.getSnapshot(),
|
|
118
120
|
slateEditorInstance: slateEditor.instance,
|
|
119
121
|
}),
|
|
120
|
-
registerBehavior: (
|
|
122
|
+
registerBehavior: (behaviorConfig) => {
|
|
123
|
+
const priority = createEditorPriority({
|
|
124
|
+
name: 'custom',
|
|
125
|
+
reference: {
|
|
126
|
+
priority: corePriority,
|
|
127
|
+
importance: 'higher',
|
|
128
|
+
},
|
|
129
|
+
})
|
|
130
|
+
const behaviorConfigWithPriority = {
|
|
131
|
+
...behaviorConfig,
|
|
132
|
+
priority,
|
|
133
|
+
}
|
|
134
|
+
|
|
121
135
|
editorActor.send({
|
|
122
136
|
type: 'add behavior',
|
|
123
|
-
|
|
137
|
+
behaviorConfig: behaviorConfigWithPriority,
|
|
124
138
|
})
|
|
125
139
|
|
|
126
140
|
return () => {
|
|
127
141
|
editorActor.send({
|
|
128
142
|
type: 'remove behavior',
|
|
129
|
-
|
|
143
|
+
behaviorConfig: behaviorConfigWithPriority,
|
|
130
144
|
})
|
|
131
145
|
}
|
|
132
146
|
},
|
|
133
147
|
send: (event) => {
|
|
134
148
|
switch (event.type) {
|
|
135
|
-
case 'add behavior':
|
|
136
|
-
case 'remove behavior':
|
|
137
149
|
case 'update key generator':
|
|
138
150
|
case 'update readOnly':
|
|
139
151
|
case 'patches':
|
|
@@ -11,12 +11,13 @@ import {
|
|
|
11
11
|
setup,
|
|
12
12
|
type ActorRefFrom,
|
|
13
13
|
} from 'xstate'
|
|
14
|
-
import {
|
|
14
|
+
import type {BehaviorConfig} from '../behaviors/behavior.config'
|
|
15
|
+
import {coreBehaviorsConfig} from '../behaviors/behavior.core'
|
|
15
16
|
import {performEvent} from '../behaviors/behavior.perform-event'
|
|
16
|
-
import type {Behavior} from '../behaviors/behavior.types.behavior'
|
|
17
17
|
import type {BehaviorEvent} from '../behaviors/behavior.types.event'
|
|
18
18
|
import type {Converter} from '../converters/converter.types'
|
|
19
19
|
import type {EventPosition} from '../internal-utils/event-position'
|
|
20
|
+
import {sortByPriority} from '../priority/priority.sort'
|
|
20
21
|
import type {NamespaceEvent} from '../type-utils'
|
|
21
22
|
import type {
|
|
22
23
|
EditorSelection,
|
|
@@ -55,14 +56,6 @@ export type MutationEvent = {
|
|
|
55
56
|
* @public
|
|
56
57
|
*/
|
|
57
58
|
export type ExternalEditorEvent =
|
|
58
|
-
| {
|
|
59
|
-
type: 'add behavior'
|
|
60
|
-
behavior: Behavior
|
|
61
|
-
}
|
|
62
|
-
| {
|
|
63
|
-
type: 'remove behavior'
|
|
64
|
-
behavior: Behavior
|
|
65
|
-
}
|
|
66
59
|
| {
|
|
67
60
|
type: 'update readOnly'
|
|
68
61
|
readOnly: boolean
|
|
@@ -160,6 +153,14 @@ export type HasTag = ReturnType<EditorActor['getSnapshot']>['hasTag']
|
|
|
160
153
|
*/
|
|
161
154
|
export type InternalEditorEvent =
|
|
162
155
|
| ExternalEditorEvent
|
|
156
|
+
| {
|
|
157
|
+
type: 'add behavior'
|
|
158
|
+
behaviorConfig: BehaviorConfig
|
|
159
|
+
}
|
|
160
|
+
| {
|
|
161
|
+
type: 'remove behavior'
|
|
162
|
+
behaviorConfig: BehaviorConfig
|
|
163
|
+
}
|
|
163
164
|
| {
|
|
164
165
|
type: 'blur'
|
|
165
166
|
editor: PortableTextSlateEditor
|
|
@@ -213,7 +214,7 @@ export type InternalEditorEmittedEvent =
|
|
|
213
214
|
export const editorMachine = setup({
|
|
214
215
|
types: {
|
|
215
216
|
context: {} as {
|
|
216
|
-
behaviors: Set<
|
|
217
|
+
behaviors: Set<BehaviorConfig>
|
|
217
218
|
converters: Set<Converter>
|
|
218
219
|
getLegacySchema: () => PortableTextMemberSchemaTypes
|
|
219
220
|
keyGenerator: () => string
|
|
@@ -248,14 +249,14 @@ export const editorMachine = setup({
|
|
|
248
249
|
behaviors: ({context, event}) => {
|
|
249
250
|
assertEvent(event, 'add behavior')
|
|
250
251
|
|
|
251
|
-
return new Set([...context.behaviors, event.
|
|
252
|
+
return new Set([...context.behaviors, event.behaviorConfig])
|
|
252
253
|
},
|
|
253
254
|
}),
|
|
254
255
|
'remove behavior from context': assign({
|
|
255
256
|
behaviors: ({context, event}) => {
|
|
256
257
|
assertEvent(event, 'remove behavior')
|
|
257
258
|
|
|
258
|
-
context.behaviors.delete(event.
|
|
259
|
+
context.behaviors.delete(event.behaviorConfig)
|
|
259
260
|
|
|
260
261
|
return new Set([...context.behaviors])
|
|
261
262
|
},
|
|
@@ -342,13 +343,15 @@ export const editorMachine = setup({
|
|
|
342
343
|
assertEvent(event, ['behavior event'])
|
|
343
344
|
|
|
344
345
|
try {
|
|
346
|
+
const behaviors = sortByPriority([
|
|
347
|
+
...context.behaviors.values(),
|
|
348
|
+
...coreBehaviorsConfig,
|
|
349
|
+
]).map((config) => config.behavior)
|
|
350
|
+
|
|
345
351
|
performEvent({
|
|
346
352
|
mode: 'raise',
|
|
347
|
-
behaviors
|
|
348
|
-
remainingEventBehaviors:
|
|
349
|
-
...context.behaviors.values(),
|
|
350
|
-
...coreBehaviors,
|
|
351
|
-
],
|
|
353
|
+
behaviors,
|
|
354
|
+
remainingEventBehaviors: behaviors,
|
|
352
355
|
event: event.behaviorEvent,
|
|
353
356
|
editor: event.editor,
|
|
354
357
|
keyGenerator: context.keyGenerator,
|
|
@@ -49,7 +49,7 @@ export function parseBlock({
|
|
|
49
49
|
)
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
function parseBlockObject({
|
|
52
|
+
export function parseBlockObject({
|
|
53
53
|
blockObject,
|
|
54
54
|
context,
|
|
55
55
|
options,
|
|
@@ -104,7 +104,7 @@ export function isTextBlock(
|
|
|
104
104
|
)
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
function parseTextBlock({
|
|
107
|
+
export function parseTextBlock({
|
|
108
108
|
block,
|
|
109
109
|
context,
|
|
110
110
|
options,
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import {describe, expect, test} from 'vitest'
|
|
2
|
+
import {sortByPriority} from './priority.sort'
|
|
3
|
+
import {createEditorPriority} from './priority.types'
|
|
4
|
+
|
|
5
|
+
describe(sortByPriority.name, () => {
|
|
6
|
+
test('empty array', () => {
|
|
7
|
+
const result = sortByPriority([])
|
|
8
|
+
expect(result).toEqual([])
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test('single item', () => {
|
|
12
|
+
const item = {priority: createEditorPriority({name: 'single'})}
|
|
13
|
+
const result = sortByPriority([item])
|
|
14
|
+
expect(result).toEqual([item])
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
test('two sets of priorities', () => {
|
|
18
|
+
const a = createEditorPriority({name: 'a'})
|
|
19
|
+
const b = createEditorPriority({
|
|
20
|
+
name: 'b',
|
|
21
|
+
reference: {priority: a, importance: 'higher'},
|
|
22
|
+
})
|
|
23
|
+
const b1 = createEditorPriority({
|
|
24
|
+
name: 'b1',
|
|
25
|
+
reference: {priority: b, importance: 'lower'},
|
|
26
|
+
})
|
|
27
|
+
const b2 = createEditorPriority({
|
|
28
|
+
name: 'b2',
|
|
29
|
+
reference: {priority: b, importance: 'lower'},
|
|
30
|
+
})
|
|
31
|
+
const c = createEditorPriority({
|
|
32
|
+
name: 'c',
|
|
33
|
+
reference: {priority: a, importance: 'higher'},
|
|
34
|
+
})
|
|
35
|
+
const c1 = createEditorPriority({
|
|
36
|
+
name: 'c1',
|
|
37
|
+
reference: {priority: c, importance: 'lower'},
|
|
38
|
+
})
|
|
39
|
+
const c2 = createEditorPriority({
|
|
40
|
+
name: 'c2',
|
|
41
|
+
reference: {priority: c, importance: 'lower'},
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const items = [
|
|
45
|
+
{priority: c1},
|
|
46
|
+
{priority: c2},
|
|
47
|
+
{priority: a},
|
|
48
|
+
{priority: b1},
|
|
49
|
+
{priority: b2},
|
|
50
|
+
{priority: b},
|
|
51
|
+
{priority: c},
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
expect(sortByPriority(items).map((item) => item.priority.name)).toEqual([
|
|
55
|
+
'b',
|
|
56
|
+
'c',
|
|
57
|
+
'b1',
|
|
58
|
+
'b2',
|
|
59
|
+
'c1',
|
|
60
|
+
'c2',
|
|
61
|
+
'a',
|
|
62
|
+
])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('sub-priorities', () => {
|
|
66
|
+
const a = createEditorPriority({name: 'a'})
|
|
67
|
+
const b = createEditorPriority({
|
|
68
|
+
name: 'b',
|
|
69
|
+
reference: {priority: a, importance: 'lower'},
|
|
70
|
+
})
|
|
71
|
+
const b1 = createEditorPriority({
|
|
72
|
+
name: 'b1',
|
|
73
|
+
reference: {priority: b, importance: 'lower'},
|
|
74
|
+
})
|
|
75
|
+
const b2 = createEditorPriority({
|
|
76
|
+
name: 'b2',
|
|
77
|
+
reference: {priority: b1, importance: 'higher'},
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const items = [{priority: b2}, {priority: b1}, {priority: a}]
|
|
81
|
+
|
|
82
|
+
expect(sortByPriority(items).map((item) => item.priority.name)).toEqual([
|
|
83
|
+
'a',
|
|
84
|
+
'b2',
|
|
85
|
+
'b1',
|
|
86
|
+
])
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('direct higher reference', () => {
|
|
90
|
+
const a = createEditorPriority({
|
|
91
|
+
name: 'a',
|
|
92
|
+
})
|
|
93
|
+
const b = createEditorPriority({
|
|
94
|
+
name: 'b',
|
|
95
|
+
reference: {priority: a, importance: 'higher'},
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const items = [{priority: b}, {priority: a}]
|
|
99
|
+
|
|
100
|
+
expect(sortByPriority(items).map((item) => item.priority.name)).toEqual([
|
|
101
|
+
'b',
|
|
102
|
+
'a',
|
|
103
|
+
])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('direct lower reference', () => {
|
|
107
|
+
const a = createEditorPriority({name: 'a'})
|
|
108
|
+
const b = createEditorPriority({
|
|
109
|
+
name: 'b',
|
|
110
|
+
reference: {priority: a, importance: 'lower'},
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
const items = [{priority: b}, {priority: a}]
|
|
114
|
+
|
|
115
|
+
expect(sortByPriority(items).map((item) => item.priority.name)).toEqual([
|
|
116
|
+
'a',
|
|
117
|
+
'b',
|
|
118
|
+
])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test('transitive references', () => {
|
|
122
|
+
const a = createEditorPriority({name: 'a'})
|
|
123
|
+
const b = createEditorPriority({
|
|
124
|
+
name: 'b',
|
|
125
|
+
reference: {priority: a, importance: 'lower'},
|
|
126
|
+
})
|
|
127
|
+
const c = createEditorPriority({
|
|
128
|
+
name: 'c',
|
|
129
|
+
reference: {priority: b, importance: 'higher'},
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
const items = [{priority: c}, {priority: b}, {priority: a}]
|
|
133
|
+
|
|
134
|
+
expect(sortByPriority(items).map((item) => item.priority.name)).toEqual([
|
|
135
|
+
'a',
|
|
136
|
+
'c',
|
|
137
|
+
'b',
|
|
138
|
+
])
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('transitive references #2', () => {
|
|
142
|
+
const a = createEditorPriority({name: 'a'})
|
|
143
|
+
const b = createEditorPriority({
|
|
144
|
+
name: 'b',
|
|
145
|
+
reference: {priority: a, importance: 'higher'},
|
|
146
|
+
})
|
|
147
|
+
const c = createEditorPriority({
|
|
148
|
+
name: 'c',
|
|
149
|
+
reference: {priority: b, importance: 'lower'},
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
const items = [{priority: c}, {priority: b}, {priority: a}]
|
|
153
|
+
|
|
154
|
+
expect(sortByPriority(items).map((item) => item.priority.name)).toEqual([
|
|
155
|
+
'b',
|
|
156
|
+
'c',
|
|
157
|
+
'a',
|
|
158
|
+
])
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
test('references to missing priorities', () => {
|
|
162
|
+
const a = createEditorPriority({name: 'a'})
|
|
163
|
+
const b = createEditorPriority({
|
|
164
|
+
name: 'b',
|
|
165
|
+
reference: {priority: a, importance: 'lower'},
|
|
166
|
+
})
|
|
167
|
+
const c = createEditorPriority({
|
|
168
|
+
name: 'c',
|
|
169
|
+
reference: {priority: b, importance: 'higher'},
|
|
170
|
+
})
|
|
171
|
+
const items = [{priority: a}, {priority: c}]
|
|
172
|
+
|
|
173
|
+
expect(sortByPriority(items).map((item) => item.priority.name)).toEqual([
|
|
174
|
+
'a',
|
|
175
|
+
'c',
|
|
176
|
+
])
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
test('references to missing priorities #2', () => {
|
|
180
|
+
const a = createEditorPriority({name: 'a'})
|
|
181
|
+
const b = createEditorPriority({
|
|
182
|
+
name: 'b',
|
|
183
|
+
reference: {priority: a, importance: 'higher'},
|
|
184
|
+
})
|
|
185
|
+
const c = createEditorPriority({
|
|
186
|
+
name: 'c',
|
|
187
|
+
reference: {priority: b, importance: 'lower'},
|
|
188
|
+
})
|
|
189
|
+
const items = [{priority: a}, {priority: c}]
|
|
190
|
+
|
|
191
|
+
expect(sortByPriority(items).map((item) => item.priority.name)).toEqual([
|
|
192
|
+
'c',
|
|
193
|
+
'a',
|
|
194
|
+
])
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
test('complex reference chains', () => {
|
|
198
|
+
const a = createEditorPriority({
|
|
199
|
+
name: 'a',
|
|
200
|
+
})
|
|
201
|
+
const b = createEditorPriority({
|
|
202
|
+
name: 'b',
|
|
203
|
+
reference: {priority: a, importance: 'lower'},
|
|
204
|
+
})
|
|
205
|
+
const c = createEditorPriority({
|
|
206
|
+
name: 'c',
|
|
207
|
+
reference: {priority: b, importance: 'higher'},
|
|
208
|
+
})
|
|
209
|
+
const d = createEditorPriority({
|
|
210
|
+
name: 'd',
|
|
211
|
+
reference: {priority: c, importance: 'lower'},
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
const items = [{priority: d}, {priority: c}, {priority: b}, {priority: a}]
|
|
215
|
+
|
|
216
|
+
const result = sortByPriority(items)
|
|
217
|
+
expect(result.map((item) => item.priority.name)).toEqual([
|
|
218
|
+
'a',
|
|
219
|
+
'c',
|
|
220
|
+
'd',
|
|
221
|
+
'b',
|
|
222
|
+
])
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('complex reference chains #2', () => {
|
|
226
|
+
const a = createEditorPriority({
|
|
227
|
+
name: 'a',
|
|
228
|
+
})
|
|
229
|
+
const b = createEditorPriority({
|
|
230
|
+
name: 'b',
|
|
231
|
+
reference: {priority: a, importance: 'higher'},
|
|
232
|
+
})
|
|
233
|
+
const c = createEditorPriority({
|
|
234
|
+
name: 'c',
|
|
235
|
+
reference: {priority: b, importance: 'lower'},
|
|
236
|
+
})
|
|
237
|
+
const d = createEditorPriority({
|
|
238
|
+
name: 'd',
|
|
239
|
+
reference: {priority: c, importance: 'higher'},
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
const items = [{priority: d}, {priority: c}, {priority: b}, {priority: a}]
|
|
243
|
+
|
|
244
|
+
const result = sortByPriority(items)
|
|
245
|
+
expect(result.map((item) => item.priority.name)).toEqual([
|
|
246
|
+
'b',
|
|
247
|
+
'd',
|
|
248
|
+
'c',
|
|
249
|
+
'a',
|
|
250
|
+
])
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
test('multiple independent chains', () => {
|
|
254
|
+
const a1 = createEditorPriority({name: 'a1'})
|
|
255
|
+
const a2 = createEditorPriority({
|
|
256
|
+
name: 'a2',
|
|
257
|
+
reference: {priority: a1, importance: 'lower'},
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
const b1 = createEditorPriority({name: 'b1'})
|
|
261
|
+
const b2 = createEditorPriority({
|
|
262
|
+
name: 'b2',
|
|
263
|
+
reference: {priority: b1, importance: 'lower'},
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const items = [
|
|
267
|
+
{priority: a2},
|
|
268
|
+
{priority: b1},
|
|
269
|
+
{priority: a1},
|
|
270
|
+
{priority: b2},
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
const result = sortByPriority(items)
|
|
274
|
+
const names = result.map((item) => item.priority.name)
|
|
275
|
+
|
|
276
|
+
// Verify that a1 comes before a2 and b1 comes before b2
|
|
277
|
+
expect(names.indexOf('a1')).toBeLessThan(names.indexOf('a2'))
|
|
278
|
+
expect(names.indexOf('b1')).toBeLessThan(names.indexOf('b2'))
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
test('cyclic references', () => {
|
|
282
|
+
const a = createEditorPriority({name: 'a'})
|
|
283
|
+
const b = createEditorPriority({
|
|
284
|
+
name: 'b',
|
|
285
|
+
reference: {priority: a, importance: 'lower'},
|
|
286
|
+
})
|
|
287
|
+
const c = createEditorPriority({
|
|
288
|
+
name: 'c',
|
|
289
|
+
reference: {priority: b, importance: 'lower'},
|
|
290
|
+
})
|
|
291
|
+
a.reference = {priority: c, importance: 'lower'}
|
|
292
|
+
|
|
293
|
+
const items = [{priority: a}, {priority: b}, {priority: c}]
|
|
294
|
+
|
|
295
|
+
expect(() => sortByPriority(items)).toThrow()
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test('missing priorities', () => {
|
|
299
|
+
const a = createEditorPriority({name: 'a'})
|
|
300
|
+
const b = createEditorPriority({
|
|
301
|
+
name: 'b',
|
|
302
|
+
reference: {priority: a, importance: 'lower'},
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
const items = [
|
|
306
|
+
{name: 'd'},
|
|
307
|
+
{name: 'c'},
|
|
308
|
+
{priority: a, name: 'a'},
|
|
309
|
+
{priority: b, name: 'b'},
|
|
310
|
+
]
|
|
311
|
+
|
|
312
|
+
expect(sortByPriority(items).map((item) => item.name)).toEqual([
|
|
313
|
+
'a',
|
|
314
|
+
'b',
|
|
315
|
+
'd',
|
|
316
|
+
'c',
|
|
317
|
+
])
|
|
318
|
+
})
|
|
319
|
+
})
|