@portabletext/plugin-markdown-shortcuts 4.0.22 → 4.0.24
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/package.json +9 -10
- package/src/behavior.markdown-shortcuts.ts +0 -133
- package/src/behavior.markdown.feature +0 -108
- package/src/behavior.markdown.test.tsx +0 -63
- package/src/global.d.ts +0 -4
- package/src/index.ts +0 -1
- package/src/plugin.markdown-shortcuts.tsx +0 -233
- package/src/rule.blockquote.ts +0 -63
- package/src/rule.heading.ts +0 -74
- package/src/rule.horizontal-rule.ts +0 -64
- package/src/rule.markdown-link.feature +0 -56
- package/src/rule.markdown-link.test.tsx +0 -44
- package/src/rule.markdown-link.ts +0 -108
- package/src/rule.ordered-list.ts +0 -66
- package/src/rule.unordered-list.ts +0 -66
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@portabletext/plugin-markdown-shortcuts",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.24",
|
|
4
4
|
"description": "Adds helpful Markdown shortcuts to the editor",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"portabletext",
|
|
@@ -32,13 +32,12 @@
|
|
|
32
32
|
"main": "./dist/index.js",
|
|
33
33
|
"types": "./dist/index.d.ts",
|
|
34
34
|
"files": [
|
|
35
|
-
"src",
|
|
36
35
|
"dist"
|
|
37
36
|
],
|
|
38
37
|
"dependencies": {
|
|
39
|
-
"react-compiler-runtime": "1.0.0",
|
|
40
|
-
"@portabletext/plugin-character-pair-decorator": "^4.0.
|
|
41
|
-
"@portabletext/plugin-input-rule": "^1.0.
|
|
38
|
+
"react-compiler-runtime": "^1.0.0",
|
|
39
|
+
"@portabletext/plugin-character-pair-decorator": "^4.0.24",
|
|
40
|
+
"@portabletext/plugin-input-rule": "^1.0.24"
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"@sanity/tsconfig": "^1.0.0",
|
|
@@ -46,19 +45,19 @@
|
|
|
46
45
|
"@vitejs/plugin-react": "^5.0.4",
|
|
47
46
|
"@vitest/browser": "^4.0.14",
|
|
48
47
|
"@vitest/browser-playwright": "^4.0.14",
|
|
49
|
-
"babel-plugin-react-compiler": "1.0.0",
|
|
48
|
+
"babel-plugin-react-compiler": "^1.0.0",
|
|
50
49
|
"eslint": "^9.39.1",
|
|
51
|
-
"eslint-plugin-react-hooks": "7.0.1",
|
|
50
|
+
"eslint-plugin-react-hooks": "^7.0.1",
|
|
52
51
|
"react": "^19.2.1",
|
|
53
52
|
"typescript": "5.9.3",
|
|
54
53
|
"typescript-eslint": "^8.48.0",
|
|
55
54
|
"vitest": "^4.0.14",
|
|
56
|
-
"@portabletext/editor": "^3.3.
|
|
57
|
-
"racejar": "2.0.
|
|
55
|
+
"@portabletext/editor": "^3.3.4",
|
|
56
|
+
"racejar": "2.0.1"
|
|
58
57
|
},
|
|
59
58
|
"peerDependencies": {
|
|
60
59
|
"react": "^18.3 || ^19",
|
|
61
|
-
"@portabletext/editor": "^3.3.
|
|
60
|
+
"@portabletext/editor": "^3.3.4"
|
|
62
61
|
},
|
|
63
62
|
"engines": {
|
|
64
63
|
"node": ">=20.19 <22 || >=22.12"
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import type {EditorContext} from '@portabletext/editor'
|
|
2
|
-
import {defineBehavior, raise} from '@portabletext/editor/behaviors'
|
|
3
|
-
import * as selectors from '@portabletext/editor/selectors'
|
|
4
|
-
|
|
5
|
-
export type ObjectWithOptionalKey = {
|
|
6
|
-
_type: string
|
|
7
|
-
_key?: string
|
|
8
|
-
[other: string]: unknown
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export type MarkdownBehaviorsConfig = {
|
|
12
|
-
horizontalRuleObject?: ({
|
|
13
|
-
context,
|
|
14
|
-
}: {
|
|
15
|
-
context: Pick<EditorContext, 'schema' | 'keyGenerator'>
|
|
16
|
-
}) => ObjectWithOptionalKey | undefined
|
|
17
|
-
defaultStyle?: ({
|
|
18
|
-
context,
|
|
19
|
-
schema,
|
|
20
|
-
}: {
|
|
21
|
-
context: Pick<EditorContext, 'schema'>
|
|
22
|
-
/**
|
|
23
|
-
* @deprecated Use `context.schema` instead
|
|
24
|
-
*/
|
|
25
|
-
schema: EditorContext['schema']
|
|
26
|
-
}) => string | undefined
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export function createMarkdownBehaviors(config: MarkdownBehaviorsConfig) {
|
|
30
|
-
const automaticHrOnPaste = defineBehavior({
|
|
31
|
-
on: 'clipboard.paste',
|
|
32
|
-
guard: ({snapshot, event}) => {
|
|
33
|
-
const text = event.originEvent.dataTransfer.getData('text/plain')
|
|
34
|
-
const hrRegExp = /^(---)$|^(—-)$|^(___)$|^(\*\*\*)$/
|
|
35
|
-
const hrCharacters = text.match(hrRegExp)?.[0]
|
|
36
|
-
const hrObject = config.horizontalRuleObject?.({
|
|
37
|
-
context: {
|
|
38
|
-
schema: snapshot.context.schema,
|
|
39
|
-
keyGenerator: snapshot.context.keyGenerator,
|
|
40
|
-
},
|
|
41
|
-
})
|
|
42
|
-
const focusBlock = selectors.getFocusBlock(snapshot)
|
|
43
|
-
const focusTextBlock = selectors.getFocusTextBlock(snapshot)
|
|
44
|
-
|
|
45
|
-
if (!hrCharacters || !hrObject || !focusBlock) {
|
|
46
|
-
return false
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return {hrCharacters, hrObject, focusBlock, focusTextBlock}
|
|
50
|
-
},
|
|
51
|
-
actions: [
|
|
52
|
-
(_, {hrCharacters}) => [
|
|
53
|
-
raise({
|
|
54
|
-
type: 'insert.text',
|
|
55
|
-
text: hrCharacters,
|
|
56
|
-
}),
|
|
57
|
-
],
|
|
58
|
-
({snapshot}, {hrObject, focusBlock, focusTextBlock}) =>
|
|
59
|
-
focusTextBlock
|
|
60
|
-
? [
|
|
61
|
-
raise({
|
|
62
|
-
type: 'insert.block',
|
|
63
|
-
block: {
|
|
64
|
-
_type: snapshot.context.schema.block.name,
|
|
65
|
-
children: focusTextBlock.node.children,
|
|
66
|
-
},
|
|
67
|
-
placement: 'after',
|
|
68
|
-
}),
|
|
69
|
-
raise({
|
|
70
|
-
type: 'insert.block',
|
|
71
|
-
block: hrObject,
|
|
72
|
-
placement: 'after',
|
|
73
|
-
}),
|
|
74
|
-
raise({
|
|
75
|
-
type: 'delete.block',
|
|
76
|
-
at: focusBlock.path,
|
|
77
|
-
}),
|
|
78
|
-
]
|
|
79
|
-
: [
|
|
80
|
-
raise({
|
|
81
|
-
type: 'insert.block',
|
|
82
|
-
block: hrObject,
|
|
83
|
-
placement: 'after',
|
|
84
|
-
}),
|
|
85
|
-
],
|
|
86
|
-
],
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
const clearStyleOnBackspace = defineBehavior({
|
|
90
|
-
on: 'delete.backward',
|
|
91
|
-
guard: ({snapshot}) => {
|
|
92
|
-
const selectionCollapsed = selectors.isSelectionCollapsed(snapshot)
|
|
93
|
-
const focusTextBlock = selectors.getFocusTextBlock(snapshot)
|
|
94
|
-
const focusSpan = selectors.getFocusSpan(snapshot)
|
|
95
|
-
|
|
96
|
-
if (!selectionCollapsed || !focusTextBlock || !focusSpan) {
|
|
97
|
-
return false
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const atTheBeginningOfBLock =
|
|
101
|
-
focusTextBlock.node.children[0]._key === focusSpan.node._key &&
|
|
102
|
-
snapshot.context.selection?.focus.offset === 0
|
|
103
|
-
|
|
104
|
-
const defaultStyle = config.defaultStyle?.({
|
|
105
|
-
context: {schema: snapshot.context.schema},
|
|
106
|
-
schema: snapshot.context.schema,
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
if (
|
|
110
|
-
atTheBeginningOfBLock &&
|
|
111
|
-
defaultStyle &&
|
|
112
|
-
focusTextBlock.node.style !== defaultStyle
|
|
113
|
-
) {
|
|
114
|
-
return {defaultStyle, focusTextBlock}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return false
|
|
118
|
-
},
|
|
119
|
-
actions: [
|
|
120
|
-
(_, {defaultStyle, focusTextBlock}) => [
|
|
121
|
-
raise({
|
|
122
|
-
type: 'block.set',
|
|
123
|
-
props: {style: defaultStyle},
|
|
124
|
-
at: focusTextBlock.path,
|
|
125
|
-
}),
|
|
126
|
-
],
|
|
127
|
-
],
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const markdownBehaviors = [automaticHrOnPaste, clearStyleOnBackspace]
|
|
131
|
-
|
|
132
|
-
return markdownBehaviors
|
|
133
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
Feature: Markdown Behaviors
|
|
2
|
-
|
|
3
|
-
Background:
|
|
4
|
-
Given a global keymap
|
|
5
|
-
|
|
6
|
-
Scenario: Automatic blockquote
|
|
7
|
-
Given the text ">"
|
|
8
|
-
When "{Space}" is pressed
|
|
9
|
-
Then the text is "q:"
|
|
10
|
-
|
|
11
|
-
Scenario: Automatic blockquote not toggled by space in the beginning
|
|
12
|
-
Given the text ">"
|
|
13
|
-
When the caret is put before ">"
|
|
14
|
-
When "{Space}" is pressed
|
|
15
|
-
Then the text is " >"
|
|
16
|
-
|
|
17
|
-
Scenario: Automatic blockquote in non-empty block
|
|
18
|
-
Given the text ">foo"
|
|
19
|
-
When the caret is put before "f"
|
|
20
|
-
And "{Space}" is pressed
|
|
21
|
-
Then the text is "q:foo"
|
|
22
|
-
|
|
23
|
-
Scenario Outline: Automatic headings
|
|
24
|
-
Given the text <text>
|
|
25
|
-
When "{Space}" is pressed
|
|
26
|
-
Then the text is <new text>
|
|
27
|
-
|
|
28
|
-
Examples:
|
|
29
|
-
| text | new text |
|
|
30
|
-
| "#" | "h1:" |
|
|
31
|
-
| "##" | "h2:" |
|
|
32
|
-
| "###" | "h3:" |
|
|
33
|
-
| "####" | "h4:" |
|
|
34
|
-
| "#####" | "h5:" |
|
|
35
|
-
| "######" | "h6:" |
|
|
36
|
-
| "#######" | "####### " |
|
|
37
|
-
|
|
38
|
-
Scenario Outline: Automatic headings not toggled by space in the beginning
|
|
39
|
-
Given the text <text>
|
|
40
|
-
When the caret is put <position>
|
|
41
|
-
When "{Space}" is pressed
|
|
42
|
-
Then the text is <new text>
|
|
43
|
-
|
|
44
|
-
Examples:
|
|
45
|
-
| text | position | new text |
|
|
46
|
-
| "#" | before "#" | " #" |
|
|
47
|
-
| "##" | before "##" | " ##" |
|
|
48
|
-
| "###" | before "###" | " ###" |
|
|
49
|
-
|
|
50
|
-
Scenario Outline: Automatic headings toggled by space mid-heading
|
|
51
|
-
Given the text <text>
|
|
52
|
-
When the caret is put <position>
|
|
53
|
-
When "{ArrowRight}" is pressed
|
|
54
|
-
When "{Space}" is pressed
|
|
55
|
-
Then the text is <new text>
|
|
56
|
-
|
|
57
|
-
Examples:
|
|
58
|
-
| text | position | new text |
|
|
59
|
-
| "##" | before "##" | "h1:#" |
|
|
60
|
-
| "###" | before "###" | "h1:##" |
|
|
61
|
-
|
|
62
|
-
Scenario Outline: Automatic headings in non-empty block
|
|
63
|
-
Given the text <text>
|
|
64
|
-
When the caret is put <position>
|
|
65
|
-
And "{Space}" is pressed
|
|
66
|
-
Then the text is <new text>
|
|
67
|
-
|
|
68
|
-
Examples:
|
|
69
|
-
| text | position | new text |
|
|
70
|
-
| "foo" | before "foo" | " foo" |
|
|
71
|
-
| "#foo" | before "foo" | "h1:foo" |
|
|
72
|
-
| "##foo" | before "foo" | "h2:foo" |
|
|
73
|
-
| "###foo" | before "foo" | "h3:foo" |
|
|
74
|
-
| "####foo" | before "foo" | "h4:foo" |
|
|
75
|
-
| "#####foo" | before "foo" | "h5:foo" |
|
|
76
|
-
| "######foo" | before "foo" | "h6:foo" |
|
|
77
|
-
| "#######foo" | before "foo" | "####### foo" |
|
|
78
|
-
|
|
79
|
-
Scenario Outline: Clear style on Backspace
|
|
80
|
-
Given the text "foo"
|
|
81
|
-
When <style> is toggled
|
|
82
|
-
And "{Backspace}" is pressed 4 times
|
|
83
|
-
Then the text is ""
|
|
84
|
-
|
|
85
|
-
Examples:
|
|
86
|
-
| style |
|
|
87
|
-
| "h1" |
|
|
88
|
-
| "h2" |
|
|
89
|
-
| "h3" |
|
|
90
|
-
| "h4" |
|
|
91
|
-
| "h5" |
|
|
92
|
-
| "h6" |
|
|
93
|
-
|
|
94
|
-
Scenario Outline: Clear style on Backspace in empty block
|
|
95
|
-
Given the text "foo"
|
|
96
|
-
When <style> is toggled
|
|
97
|
-
And "{Backspace}" is pressed 4 times
|
|
98
|
-
And "bar" is typed
|
|
99
|
-
Then the text is "bar"
|
|
100
|
-
|
|
101
|
-
Examples:
|
|
102
|
-
| style |
|
|
103
|
-
| "h1" |
|
|
104
|
-
| "h2" |
|
|
105
|
-
| "h3" |
|
|
106
|
-
| "h4" |
|
|
107
|
-
| "h5" |
|
|
108
|
-
| "h6" |
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import {defineSchema} from '@portabletext/editor'
|
|
2
|
-
import {parameterTypes} from '@portabletext/editor/test'
|
|
3
|
-
import {
|
|
4
|
-
createTestEditor,
|
|
5
|
-
stepDefinitions,
|
|
6
|
-
type Context,
|
|
7
|
-
} from '@portabletext/editor/test/vitest'
|
|
8
|
-
import {Before} from 'racejar'
|
|
9
|
-
import {Feature} from 'racejar/vitest'
|
|
10
|
-
import behaviorMarkdownFeature from './behavior.markdown.feature?raw'
|
|
11
|
-
import {MarkdownShortcutsPlugin} from './plugin.markdown-shortcuts'
|
|
12
|
-
|
|
13
|
-
Feature({
|
|
14
|
-
hooks: [
|
|
15
|
-
Before(async (context: Context) => {
|
|
16
|
-
const {editor, locator} = await createTestEditor({
|
|
17
|
-
children: (
|
|
18
|
-
<MarkdownShortcutsPlugin
|
|
19
|
-
defaultStyle={({context}) => context.schema.styles[0]?.name}
|
|
20
|
-
headingStyle={({context, props}) =>
|
|
21
|
-
context.schema.styles.find(
|
|
22
|
-
(style) => style.name === `h${props.level}`,
|
|
23
|
-
)?.name
|
|
24
|
-
}
|
|
25
|
-
blockquoteStyle={({context}) =>
|
|
26
|
-
context.schema.styles.find((style) => style.name === 'blockquote')
|
|
27
|
-
?.name
|
|
28
|
-
}
|
|
29
|
-
unorderedList={({context}) =>
|
|
30
|
-
context.schema.lists.find((list) => list.name === 'bullet')?.name
|
|
31
|
-
}
|
|
32
|
-
orderedList={({context}) =>
|
|
33
|
-
context.schema.lists.find((list) => list.name === 'number')?.name
|
|
34
|
-
}
|
|
35
|
-
/>
|
|
36
|
-
),
|
|
37
|
-
schemaDefinition: defineSchema({
|
|
38
|
-
annotations: [{name: 'comment'}, {name: 'link'}],
|
|
39
|
-
decorators: [{name: 'em'}, {name: 'strong'}],
|
|
40
|
-
blockObjects: [{name: 'image'}, {name: 'break'}],
|
|
41
|
-
inlineObjects: [{name: 'stock-ticker'}],
|
|
42
|
-
lists: [{name: 'bullet'}, {name: 'number'}],
|
|
43
|
-
styles: [
|
|
44
|
-
{name: 'normal'},
|
|
45
|
-
{name: 'h1'},
|
|
46
|
-
{name: 'h2'},
|
|
47
|
-
{name: 'h3'},
|
|
48
|
-
{name: 'h4'},
|
|
49
|
-
{name: 'h5'},
|
|
50
|
-
{name: 'h6'},
|
|
51
|
-
{name: 'blockquote'},
|
|
52
|
-
],
|
|
53
|
-
}),
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
context.locator = locator
|
|
57
|
-
context.editor = editor
|
|
58
|
-
}),
|
|
59
|
-
],
|
|
60
|
-
featureText: behaviorMarkdownFeature,
|
|
61
|
-
stepDefinitions: stepDefinitions,
|
|
62
|
-
parameterTypes,
|
|
63
|
-
})
|
package/src/global.d.ts
DELETED
package/src/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './plugin.markdown-shortcuts'
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
import type {EditorContext} from '@portabletext/editor'
|
|
2
|
-
import {useEditor} from '@portabletext/editor'
|
|
3
|
-
import {CharacterPairDecoratorPlugin} from '@portabletext/plugin-character-pair-decorator'
|
|
4
|
-
import {InputRulePlugin} from '@portabletext/plugin-input-rule'
|
|
5
|
-
import {useEffect, useMemo} from 'react'
|
|
6
|
-
import {
|
|
7
|
-
createMarkdownBehaviors,
|
|
8
|
-
type MarkdownBehaviorsConfig,
|
|
9
|
-
type ObjectWithOptionalKey,
|
|
10
|
-
} from './behavior.markdown-shortcuts'
|
|
11
|
-
import {createBlockquoteRule} from './rule.blockquote'
|
|
12
|
-
import {createHeadingRule} from './rule.heading'
|
|
13
|
-
import {createHorizontalRuleRule} from './rule.horizontal-rule'
|
|
14
|
-
import {createMarkdownLinkRule} from './rule.markdown-link'
|
|
15
|
-
import {createOrderedListRule} from './rule.ordered-list'
|
|
16
|
-
import {createUnorderedListRule} from './rule.unordered-list'
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* @public
|
|
20
|
-
*/
|
|
21
|
-
export type MarkdownShortcutsPluginProps = MarkdownBehaviorsConfig & {
|
|
22
|
-
blockquoteStyle?: ({
|
|
23
|
-
context,
|
|
24
|
-
schema,
|
|
25
|
-
}: {
|
|
26
|
-
context: Pick<EditorContext, 'schema'>
|
|
27
|
-
/**
|
|
28
|
-
* @deprecated Use `context.schema` instead
|
|
29
|
-
*/
|
|
30
|
-
schema: EditorContext['schema']
|
|
31
|
-
}) => string | undefined
|
|
32
|
-
defaultStyle?: ({
|
|
33
|
-
context,
|
|
34
|
-
schema,
|
|
35
|
-
}: {
|
|
36
|
-
context: Pick<EditorContext, 'schema'>
|
|
37
|
-
/**
|
|
38
|
-
* @deprecated Use `context.schema` instead
|
|
39
|
-
*/
|
|
40
|
-
schema: EditorContext['schema']
|
|
41
|
-
}) => string | undefined
|
|
42
|
-
headingStyle?: ({
|
|
43
|
-
context,
|
|
44
|
-
schema,
|
|
45
|
-
props,
|
|
46
|
-
level,
|
|
47
|
-
}: {
|
|
48
|
-
context: Pick<EditorContext, 'schema'>
|
|
49
|
-
/**
|
|
50
|
-
* @deprecated Use `context.schema` instead
|
|
51
|
-
*/
|
|
52
|
-
schema: EditorContext['schema']
|
|
53
|
-
props: {level: number}
|
|
54
|
-
/**
|
|
55
|
-
* @deprecated Use `props.level` instead
|
|
56
|
-
*/
|
|
57
|
-
level: number
|
|
58
|
-
}) => string | undefined
|
|
59
|
-
linkObject?: ({
|
|
60
|
-
context,
|
|
61
|
-
props,
|
|
62
|
-
}: {
|
|
63
|
-
context: Pick<EditorContext, 'schema' | 'keyGenerator'>
|
|
64
|
-
props: {href: string}
|
|
65
|
-
}) => ObjectWithOptionalKey | undefined
|
|
66
|
-
unorderedList?: ({
|
|
67
|
-
context,
|
|
68
|
-
schema,
|
|
69
|
-
}: {
|
|
70
|
-
context: Pick<EditorContext, 'schema'>
|
|
71
|
-
/**
|
|
72
|
-
* @deprecated Use `context.schema` instead
|
|
73
|
-
*/
|
|
74
|
-
schema: EditorContext['schema']
|
|
75
|
-
}) => string | undefined
|
|
76
|
-
orderedList?: ({
|
|
77
|
-
context,
|
|
78
|
-
schema,
|
|
79
|
-
}: {
|
|
80
|
-
context: Pick<EditorContext, 'schema'>
|
|
81
|
-
/**
|
|
82
|
-
* @deprecated Use `context.schema` instead
|
|
83
|
-
*/
|
|
84
|
-
schema: EditorContext['schema']
|
|
85
|
-
}) => string | undefined
|
|
86
|
-
boldDecorator?: ({
|
|
87
|
-
context,
|
|
88
|
-
schema,
|
|
89
|
-
}: {
|
|
90
|
-
context: Pick<EditorContext, 'schema'>
|
|
91
|
-
/**
|
|
92
|
-
* @deprecated Use `context.schema` instead
|
|
93
|
-
*/
|
|
94
|
-
schema: EditorContext['schema']
|
|
95
|
-
}) => string | undefined
|
|
96
|
-
codeDecorator?: ({
|
|
97
|
-
context,
|
|
98
|
-
schema,
|
|
99
|
-
}: {
|
|
100
|
-
context: Pick<EditorContext, 'schema'>
|
|
101
|
-
/**
|
|
102
|
-
* @deprecated Use `context.schema` instead
|
|
103
|
-
*/
|
|
104
|
-
schema: EditorContext['schema']
|
|
105
|
-
}) => string | undefined
|
|
106
|
-
italicDecorator?: ({
|
|
107
|
-
context,
|
|
108
|
-
schema,
|
|
109
|
-
}: {
|
|
110
|
-
context: Pick<EditorContext, 'schema'>
|
|
111
|
-
/**
|
|
112
|
-
* @deprecated Use `context.schema` instead
|
|
113
|
-
*/
|
|
114
|
-
schema: EditorContext['schema']
|
|
115
|
-
}) => string | undefined
|
|
116
|
-
strikeThroughDecorator?: ({
|
|
117
|
-
context,
|
|
118
|
-
schema,
|
|
119
|
-
}: {
|
|
120
|
-
context: Pick<EditorContext, 'schema'>
|
|
121
|
-
/**
|
|
122
|
-
* @deprecated Use `context.schema` instead
|
|
123
|
-
*/
|
|
124
|
-
schema: EditorContext['schema']
|
|
125
|
-
}) => string | undefined
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* @public
|
|
130
|
-
*/
|
|
131
|
-
export function MarkdownShortcutsPlugin({
|
|
132
|
-
blockquoteStyle,
|
|
133
|
-
boldDecorator,
|
|
134
|
-
codeDecorator,
|
|
135
|
-
defaultStyle,
|
|
136
|
-
headingStyle,
|
|
137
|
-
horizontalRuleObject,
|
|
138
|
-
linkObject,
|
|
139
|
-
italicDecorator,
|
|
140
|
-
orderedList,
|
|
141
|
-
strikeThroughDecorator,
|
|
142
|
-
unorderedList,
|
|
143
|
-
}: MarkdownShortcutsPluginProps) {
|
|
144
|
-
const editor = useEditor()
|
|
145
|
-
|
|
146
|
-
useEffect(() => {
|
|
147
|
-
const behaviors = createMarkdownBehaviors({
|
|
148
|
-
defaultStyle,
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
const unregisterBehaviors = behaviors.map((behavior) =>
|
|
152
|
-
editor.registerBehavior({behavior}),
|
|
153
|
-
)
|
|
154
|
-
|
|
155
|
-
return () => {
|
|
156
|
-
for (const unregisterBehavior of unregisterBehaviors) {
|
|
157
|
-
unregisterBehavior()
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}, [defaultStyle, editor])
|
|
161
|
-
|
|
162
|
-
const inputRules = useMemo(() => {
|
|
163
|
-
const rules = []
|
|
164
|
-
if (blockquoteStyle) {
|
|
165
|
-
rules.push(createBlockquoteRule({blockquoteStyle}))
|
|
166
|
-
}
|
|
167
|
-
if (headingStyle) {
|
|
168
|
-
rules.push(createHeadingRule({headingStyle}))
|
|
169
|
-
}
|
|
170
|
-
if (horizontalRuleObject) {
|
|
171
|
-
rules.push(createHorizontalRuleRule({horizontalRuleObject}))
|
|
172
|
-
}
|
|
173
|
-
if (linkObject) {
|
|
174
|
-
rules.push(createMarkdownLinkRule({linkObject}))
|
|
175
|
-
}
|
|
176
|
-
if (orderedList) {
|
|
177
|
-
rules.push(createOrderedListRule({orderedList}))
|
|
178
|
-
}
|
|
179
|
-
if (unorderedList) {
|
|
180
|
-
rules.push(createUnorderedListRule({unorderedList}))
|
|
181
|
-
}
|
|
182
|
-
return rules.length > 0 ? rules : null
|
|
183
|
-
}, [
|
|
184
|
-
blockquoteStyle,
|
|
185
|
-
headingStyle,
|
|
186
|
-
horizontalRuleObject,
|
|
187
|
-
linkObject,
|
|
188
|
-
orderedList,
|
|
189
|
-
unorderedList,
|
|
190
|
-
])
|
|
191
|
-
|
|
192
|
-
return (
|
|
193
|
-
<>
|
|
194
|
-
{boldDecorator ? (
|
|
195
|
-
<>
|
|
196
|
-
<CharacterPairDecoratorPlugin
|
|
197
|
-
decorator={boldDecorator}
|
|
198
|
-
pair={{char: '*', amount: 2}}
|
|
199
|
-
/>
|
|
200
|
-
<CharacterPairDecoratorPlugin
|
|
201
|
-
decorator={boldDecorator}
|
|
202
|
-
pair={{char: '_', amount: 2}}
|
|
203
|
-
/>
|
|
204
|
-
</>
|
|
205
|
-
) : null}
|
|
206
|
-
{codeDecorator ? (
|
|
207
|
-
<CharacterPairDecoratorPlugin
|
|
208
|
-
decorator={codeDecorator}
|
|
209
|
-
pair={{char: '`', amount: 1}}
|
|
210
|
-
/>
|
|
211
|
-
) : null}
|
|
212
|
-
{italicDecorator ? (
|
|
213
|
-
<>
|
|
214
|
-
<CharacterPairDecoratorPlugin
|
|
215
|
-
decorator={italicDecorator}
|
|
216
|
-
pair={{char: '*', amount: 1}}
|
|
217
|
-
/>
|
|
218
|
-
<CharacterPairDecoratorPlugin
|
|
219
|
-
decorator={italicDecorator}
|
|
220
|
-
pair={{char: '_', amount: 1}}
|
|
221
|
-
/>
|
|
222
|
-
</>
|
|
223
|
-
) : null}
|
|
224
|
-
{strikeThroughDecorator ? (
|
|
225
|
-
<CharacterPairDecoratorPlugin
|
|
226
|
-
decorator={strikeThroughDecorator}
|
|
227
|
-
pair={{char: '~', amount: 2}}
|
|
228
|
-
/>
|
|
229
|
-
) : null}
|
|
230
|
-
{inputRules ? <InputRulePlugin rules={inputRules} /> : null}
|
|
231
|
-
</>
|
|
232
|
-
)
|
|
233
|
-
}
|
package/src/rule.blockquote.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import type {EditorContext} from '@portabletext/editor'
|
|
2
|
-
import {raise} from '@portabletext/editor/behaviors'
|
|
3
|
-
import {getPreviousInlineObject} from '@portabletext/editor/selectors'
|
|
4
|
-
import {defineInputRule} from '@portabletext/plugin-input-rule'
|
|
5
|
-
|
|
6
|
-
export function createBlockquoteRule(config: {
|
|
7
|
-
blockquoteStyle: ({
|
|
8
|
-
context,
|
|
9
|
-
schema,
|
|
10
|
-
}: {
|
|
11
|
-
context: Pick<EditorContext, 'schema'>
|
|
12
|
-
/**
|
|
13
|
-
* @deprecated Use `context.schema` instead
|
|
14
|
-
*/
|
|
15
|
-
schema: EditorContext['schema']
|
|
16
|
-
}) => string | undefined
|
|
17
|
-
}) {
|
|
18
|
-
return defineInputRule({
|
|
19
|
-
on: /^> /,
|
|
20
|
-
guard: ({snapshot, event}) => {
|
|
21
|
-
const style = config.blockquoteStyle({
|
|
22
|
-
context: {schema: snapshot.context.schema},
|
|
23
|
-
schema: snapshot.context.schema,
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
if (!style) {
|
|
27
|
-
return false
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const previousInlineObject = getPreviousInlineObject(snapshot)
|
|
31
|
-
|
|
32
|
-
if (previousInlineObject) {
|
|
33
|
-
return false
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const match = event.matches.at(0)
|
|
37
|
-
|
|
38
|
-
if (!match) {
|
|
39
|
-
return false
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {style, match}
|
|
43
|
-
},
|
|
44
|
-
actions: [
|
|
45
|
-
({event}, {style, match}) => [
|
|
46
|
-
raise({
|
|
47
|
-
type: 'block.unset',
|
|
48
|
-
props: ['listItem', 'level'],
|
|
49
|
-
at: event.focusBlock.path,
|
|
50
|
-
}),
|
|
51
|
-
raise({
|
|
52
|
-
type: 'block.set',
|
|
53
|
-
props: {style},
|
|
54
|
-
at: event.focusBlock.path,
|
|
55
|
-
}),
|
|
56
|
-
raise({
|
|
57
|
-
type: 'delete',
|
|
58
|
-
at: match.targetOffsets,
|
|
59
|
-
}),
|
|
60
|
-
],
|
|
61
|
-
],
|
|
62
|
-
})
|
|
63
|
-
}
|
package/src/rule.heading.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type {EditorContext} from '@portabletext/editor'
|
|
2
|
-
import {raise} from '@portabletext/editor/behaviors'
|
|
3
|
-
import {getPreviousInlineObject} from '@portabletext/editor/selectors'
|
|
4
|
-
import {defineInputRule} from '@portabletext/plugin-input-rule'
|
|
5
|
-
|
|
6
|
-
export function createHeadingRule(config: {
|
|
7
|
-
headingStyle: ({
|
|
8
|
-
context,
|
|
9
|
-
schema,
|
|
10
|
-
props,
|
|
11
|
-
level,
|
|
12
|
-
}: {
|
|
13
|
-
context: Pick<EditorContext, 'schema'>
|
|
14
|
-
/**
|
|
15
|
-
* @deprecated Use `context.schema` instead
|
|
16
|
-
*/
|
|
17
|
-
schema: EditorContext['schema']
|
|
18
|
-
props: {level: number}
|
|
19
|
-
/**
|
|
20
|
-
* @deprecated Use `props.level` instead
|
|
21
|
-
*/
|
|
22
|
-
level: number
|
|
23
|
-
}) => string | undefined
|
|
24
|
-
}) {
|
|
25
|
-
return defineInputRule({
|
|
26
|
-
on: /^#+ /,
|
|
27
|
-
guard: ({snapshot, event}) => {
|
|
28
|
-
const previousInlineObject = getPreviousInlineObject(snapshot)
|
|
29
|
-
|
|
30
|
-
if (previousInlineObject) {
|
|
31
|
-
return false
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const match = event.matches.at(0)
|
|
35
|
-
|
|
36
|
-
if (!match) {
|
|
37
|
-
return false
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const level = match.text.length - 1
|
|
41
|
-
|
|
42
|
-
const style = config.headingStyle({
|
|
43
|
-
context: {schema: snapshot.context.schema},
|
|
44
|
-
schema: snapshot.context.schema,
|
|
45
|
-
props: {level},
|
|
46
|
-
level,
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
if (!style) {
|
|
50
|
-
return false
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return {match, style}
|
|
54
|
-
},
|
|
55
|
-
actions: [
|
|
56
|
-
({event}, {match, style}) => [
|
|
57
|
-
raise({
|
|
58
|
-
type: 'block.unset',
|
|
59
|
-
props: ['listItem', 'level'],
|
|
60
|
-
at: event.focusBlock.path,
|
|
61
|
-
}),
|
|
62
|
-
raise({
|
|
63
|
-
type: 'block.set',
|
|
64
|
-
props: {style},
|
|
65
|
-
at: event.focusBlock.path,
|
|
66
|
-
}),
|
|
67
|
-
raise({
|
|
68
|
-
type: 'delete',
|
|
69
|
-
at: match.targetOffsets,
|
|
70
|
-
}),
|
|
71
|
-
],
|
|
72
|
-
],
|
|
73
|
-
})
|
|
74
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import type {EditorContext} from '@portabletext/editor'
|
|
2
|
-
import {raise} from '@portabletext/editor/behaviors'
|
|
3
|
-
import {getPreviousInlineObject} from '@portabletext/editor/selectors'
|
|
4
|
-
import {defineInputRule} from '@portabletext/plugin-input-rule'
|
|
5
|
-
import type {ObjectWithOptionalKey} from './behavior.markdown-shortcuts'
|
|
6
|
-
|
|
7
|
-
export function createHorizontalRuleRule(config: {
|
|
8
|
-
horizontalRuleObject: ({
|
|
9
|
-
context,
|
|
10
|
-
}: {
|
|
11
|
-
context: Pick<EditorContext, 'schema' | 'keyGenerator'>
|
|
12
|
-
}) => ObjectWithOptionalKey | undefined
|
|
13
|
-
}) {
|
|
14
|
-
return defineInputRule({
|
|
15
|
-
on: /^(---)|^(—-)|^(___)|^(\*\*\*)/,
|
|
16
|
-
guard: ({snapshot, event}) => {
|
|
17
|
-
const hrObject = config.horizontalRuleObject({
|
|
18
|
-
context: {
|
|
19
|
-
schema: snapshot.context.schema,
|
|
20
|
-
keyGenerator: snapshot.context.keyGenerator,
|
|
21
|
-
},
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
if (!hrObject) {
|
|
25
|
-
// If no horizontal rule object is provided, then we can safely skip
|
|
26
|
-
// this rule.
|
|
27
|
-
return false
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const previousInlineObject = getPreviousInlineObject(snapshot)
|
|
31
|
-
|
|
32
|
-
if (previousInlineObject) {
|
|
33
|
-
// Input Rules only look at the plain text of the text block. This
|
|
34
|
-
// means that the RegExp is matched even if there is a leading inline
|
|
35
|
-
// object.
|
|
36
|
-
return false
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// In theory, an Input Rule could return multiple matches. But in this
|
|
40
|
-
// case we only expect one match.
|
|
41
|
-
const match = event.matches.at(0)
|
|
42
|
-
|
|
43
|
-
if (!match) {
|
|
44
|
-
return false
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return {hrObject, match}
|
|
48
|
-
},
|
|
49
|
-
actions: [
|
|
50
|
-
(_, {hrObject, match}) => [
|
|
51
|
-
raise({
|
|
52
|
-
type: 'insert.block',
|
|
53
|
-
block: hrObject,
|
|
54
|
-
placement: 'before',
|
|
55
|
-
select: 'none',
|
|
56
|
-
}),
|
|
57
|
-
raise({
|
|
58
|
-
type: 'delete',
|
|
59
|
-
at: match.targetOffsets,
|
|
60
|
-
}),
|
|
61
|
-
],
|
|
62
|
-
],
|
|
63
|
-
})
|
|
64
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
Feature: Markdown Link Rule
|
|
2
|
-
|
|
3
|
-
Background:
|
|
4
|
-
Given a global keymap
|
|
5
|
-
|
|
6
|
-
Scenario Outline: Transform markdown Link into annotation
|
|
7
|
-
Given the text <text>
|
|
8
|
-
When <inserted text> is inserted
|
|
9
|
-
And "new" is typed
|
|
10
|
-
Then the text is <new text>
|
|
11
|
-
And <annotated> has <marks>
|
|
12
|
-
|
|
13
|
-
Examples:
|
|
14
|
-
| text | inserted text | new text | annotated | marks |
|
|
15
|
-
| "[foo](bar" | ")" | "foo,new" | "foo" | marks "k4" |
|
|
16
|
-
| "[[foo](bar" | ")" | "[,foo,new" | "foo" | marks "k4" |
|
|
17
|
-
| "[f[oo](bar" | ")" | "[f,oo,new" | "oo" | marks "k4" |
|
|
18
|
-
| "[f[]oo](bar" | ")" | "[f[]oo](bar)new" | "" | no marks |
|
|
19
|
-
|
|
20
|
-
Scenario: Preserving decorator in link text
|
|
21
|
-
Given the text "[foo](bar"
|
|
22
|
-
And "strong" around "foo"
|
|
23
|
-
When ")" is inserted
|
|
24
|
-
And "new" is typed
|
|
25
|
-
Then the text is "foo,new"
|
|
26
|
-
And "foo" has marks "strong,k6"
|
|
27
|
-
|
|
28
|
-
Scenario: Preserving decorators in link text
|
|
29
|
-
Given the text "[foo](bar"
|
|
30
|
-
And "strong" around "foo"
|
|
31
|
-
And "em" around "oo"
|
|
32
|
-
When ")" is inserted
|
|
33
|
-
And "new" is typed
|
|
34
|
-
Then the text is "f,oo,new"
|
|
35
|
-
And "f" has marks "strong,k7"
|
|
36
|
-
And "oo" has marks "strong,em,k7"
|
|
37
|
-
|
|
38
|
-
Scenario: Overwriting other links
|
|
39
|
-
Given the text "[foo](bar"
|
|
40
|
-
And a "link" "l1" around "foo"
|
|
41
|
-
When the caret is put after "bar"
|
|
42
|
-
And ")" is inserted
|
|
43
|
-
And "new" is typed
|
|
44
|
-
Then the text is "foo,new"
|
|
45
|
-
And "foo" has an annotation different than "l1"
|
|
46
|
-
|
|
47
|
-
Scenario: Preserving other annotations
|
|
48
|
-
Given the text "[foo](bar"
|
|
49
|
-
And a "link" "l1" around "foo"
|
|
50
|
-
And a "comment" "c1" around "foo"
|
|
51
|
-
When the caret is put after "bar"
|
|
52
|
-
And ")" is inserted
|
|
53
|
-
And "new" is typed
|
|
54
|
-
Then the text is "foo,new"
|
|
55
|
-
And "foo" has an annotation different than "l1"
|
|
56
|
-
And "foo" has marks "c1,k9"
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import {defineSchema} from '@portabletext/editor'
|
|
2
|
-
import {parameterTypes} from '@portabletext/editor/test'
|
|
3
|
-
import {
|
|
4
|
-
createTestEditor,
|
|
5
|
-
stepDefinitions,
|
|
6
|
-
type Context,
|
|
7
|
-
} from '@portabletext/editor/test/vitest'
|
|
8
|
-
import {InputRulePlugin} from '@portabletext/plugin-input-rule'
|
|
9
|
-
import {Before} from 'racejar'
|
|
10
|
-
import {Feature} from 'racejar/vitest'
|
|
11
|
-
import {createMarkdownLinkRule} from './rule.markdown-link'
|
|
12
|
-
import markdownLinkFeature from './rule.markdown-link.feature?raw'
|
|
13
|
-
|
|
14
|
-
const markdownLinkRule = createMarkdownLinkRule({
|
|
15
|
-
linkObject: ({props}) => ({
|
|
16
|
-
_type: 'link',
|
|
17
|
-
href: props.href,
|
|
18
|
-
}),
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
Feature({
|
|
22
|
-
hooks: [
|
|
23
|
-
Before(async (context: Context) => {
|
|
24
|
-
const {editor, locator} = await createTestEditor({
|
|
25
|
-
children: (
|
|
26
|
-
<>
|
|
27
|
-
<InputRulePlugin rules={[markdownLinkRule]} />
|
|
28
|
-
</>
|
|
29
|
-
),
|
|
30
|
-
schemaDefinition: defineSchema({
|
|
31
|
-
decorators: [{name: 'strong'}, {name: 'em'}],
|
|
32
|
-
annotations: [{name: 'link'}, {name: 'comment'}],
|
|
33
|
-
inlineObjects: [{name: 'stock-ticker'}],
|
|
34
|
-
}),
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
context.locator = locator
|
|
38
|
-
context.editor = editor
|
|
39
|
-
}),
|
|
40
|
-
],
|
|
41
|
-
featureText: markdownLinkFeature,
|
|
42
|
-
stepDefinitions,
|
|
43
|
-
parameterTypes,
|
|
44
|
-
})
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import type {EditorContext} from '@portabletext/editor'
|
|
2
|
-
import {raise, type BehaviorAction} from '@portabletext/editor/behaviors'
|
|
3
|
-
import {defineInputRule} from '@portabletext/plugin-input-rule'
|
|
4
|
-
import type {ObjectWithOptionalKey} from './behavior.markdown-shortcuts'
|
|
5
|
-
|
|
6
|
-
export function createMarkdownLinkRule(config: {
|
|
7
|
-
linkObject: ({
|
|
8
|
-
context,
|
|
9
|
-
props,
|
|
10
|
-
}: {
|
|
11
|
-
context: Pick<EditorContext, 'schema' | 'keyGenerator'>
|
|
12
|
-
props: {href: string}
|
|
13
|
-
}) => ObjectWithOptionalKey | undefined
|
|
14
|
-
}) {
|
|
15
|
-
return defineInputRule({
|
|
16
|
-
on: /\[([^[\]]+)]\((.+)\)/,
|
|
17
|
-
actions: [
|
|
18
|
-
({snapshot, event}) => {
|
|
19
|
-
const newText = event.textBefore + event.textInserted
|
|
20
|
-
let textLengthDelta = 0
|
|
21
|
-
const actions: Array<BehaviorAction> = []
|
|
22
|
-
|
|
23
|
-
for (const match of event.matches.reverse()) {
|
|
24
|
-
const textMatch = match.groupMatches.at(0)
|
|
25
|
-
const hrefMatch = match.groupMatches.at(1)
|
|
26
|
-
|
|
27
|
-
if (textMatch === undefined || hrefMatch === undefined) {
|
|
28
|
-
continue
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
textLengthDelta =
|
|
32
|
-
textLengthDelta -
|
|
33
|
-
(match.targetOffsets.focus.offset -
|
|
34
|
-
match.targetOffsets.anchor.offset -
|
|
35
|
-
textMatch.text.length)
|
|
36
|
-
|
|
37
|
-
const linkObject = config.linkObject({
|
|
38
|
-
context: {
|
|
39
|
-
schema: snapshot.context.schema,
|
|
40
|
-
keyGenerator: snapshot.context.keyGenerator,
|
|
41
|
-
},
|
|
42
|
-
props: {href: hrefMatch.text},
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
if (!linkObject) {
|
|
46
|
-
continue
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const {_type, _key, ...value} = linkObject
|
|
50
|
-
|
|
51
|
-
const leftSideOffsets = {
|
|
52
|
-
anchor: match.targetOffsets.anchor,
|
|
53
|
-
focus: textMatch.targetOffsets.anchor,
|
|
54
|
-
}
|
|
55
|
-
const rightSideOffsets = {
|
|
56
|
-
anchor: textMatch.targetOffsets.focus,
|
|
57
|
-
focus: match.targetOffsets.focus,
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
actions.push(
|
|
61
|
-
raise({
|
|
62
|
-
type: 'select',
|
|
63
|
-
at: textMatch.targetOffsets,
|
|
64
|
-
}),
|
|
65
|
-
)
|
|
66
|
-
actions.push(
|
|
67
|
-
raise({
|
|
68
|
-
type: 'annotation.add',
|
|
69
|
-
annotation: {
|
|
70
|
-
name: _type,
|
|
71
|
-
_key,
|
|
72
|
-
value,
|
|
73
|
-
},
|
|
74
|
-
}),
|
|
75
|
-
)
|
|
76
|
-
actions.push(
|
|
77
|
-
raise({
|
|
78
|
-
type: 'delete',
|
|
79
|
-
at: rightSideOffsets,
|
|
80
|
-
}),
|
|
81
|
-
)
|
|
82
|
-
actions.push(
|
|
83
|
-
raise({
|
|
84
|
-
type: 'delete',
|
|
85
|
-
at: leftSideOffsets,
|
|
86
|
-
}),
|
|
87
|
-
)
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const endCaretPosition = {
|
|
91
|
-
path: event.focusBlock.path,
|
|
92
|
-
offset: newText.length - textLengthDelta * -1,
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return [
|
|
96
|
-
...actions,
|
|
97
|
-
raise({
|
|
98
|
-
type: 'select',
|
|
99
|
-
at: {
|
|
100
|
-
anchor: endCaretPosition,
|
|
101
|
-
focus: endCaretPosition,
|
|
102
|
-
},
|
|
103
|
-
}),
|
|
104
|
-
]
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
})
|
|
108
|
-
}
|
package/src/rule.ordered-list.ts
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type {EditorContext} from '@portabletext/editor'
|
|
2
|
-
import {raise} from '@portabletext/editor/behaviors'
|
|
3
|
-
import {getPreviousInlineObject} from '@portabletext/editor/selectors'
|
|
4
|
-
import {defineInputRule} from '@portabletext/plugin-input-rule'
|
|
5
|
-
|
|
6
|
-
export function createOrderedListRule(config: {
|
|
7
|
-
orderedList: ({
|
|
8
|
-
context,
|
|
9
|
-
schema,
|
|
10
|
-
}: {
|
|
11
|
-
context: Pick<EditorContext, 'schema'>
|
|
12
|
-
/**
|
|
13
|
-
* @deprecated Use `context.schema` instead
|
|
14
|
-
*/
|
|
15
|
-
schema: EditorContext['schema']
|
|
16
|
-
}) => string | undefined
|
|
17
|
-
}) {
|
|
18
|
-
return defineInputRule({
|
|
19
|
-
on: /^1\. /,
|
|
20
|
-
guard: ({snapshot, event}) => {
|
|
21
|
-
const orderedList = config.orderedList({
|
|
22
|
-
context: {schema: snapshot.context.schema},
|
|
23
|
-
schema: snapshot.context.schema,
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
if (!orderedList) {
|
|
27
|
-
return false
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const previousInlineObject = getPreviousInlineObject(snapshot)
|
|
31
|
-
|
|
32
|
-
if (previousInlineObject) {
|
|
33
|
-
return false
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const match = event.matches.at(0)
|
|
37
|
-
|
|
38
|
-
if (!match) {
|
|
39
|
-
return false
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {match, orderedList}
|
|
43
|
-
},
|
|
44
|
-
actions: [
|
|
45
|
-
({event}, {match, orderedList}) => [
|
|
46
|
-
raise({
|
|
47
|
-
type: 'block.unset',
|
|
48
|
-
props: ['style'],
|
|
49
|
-
at: event.focusBlock.path,
|
|
50
|
-
}),
|
|
51
|
-
raise({
|
|
52
|
-
type: 'block.set',
|
|
53
|
-
props: {
|
|
54
|
-
listItem: orderedList,
|
|
55
|
-
level: event.focusBlock.node.level ?? 1,
|
|
56
|
-
},
|
|
57
|
-
at: event.focusBlock.path,
|
|
58
|
-
}),
|
|
59
|
-
raise({
|
|
60
|
-
type: 'delete',
|
|
61
|
-
at: match.targetOffsets,
|
|
62
|
-
}),
|
|
63
|
-
],
|
|
64
|
-
],
|
|
65
|
-
})
|
|
66
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import type {EditorContext} from '@portabletext/editor'
|
|
2
|
-
import {raise} from '@portabletext/editor/behaviors'
|
|
3
|
-
import {getPreviousInlineObject} from '@portabletext/editor/selectors'
|
|
4
|
-
import {defineInputRule} from '@portabletext/plugin-input-rule'
|
|
5
|
-
|
|
6
|
-
export function createUnorderedListRule(config: {
|
|
7
|
-
unorderedList: ({
|
|
8
|
-
context,
|
|
9
|
-
schema,
|
|
10
|
-
}: {
|
|
11
|
-
context: Pick<EditorContext, 'schema'>
|
|
12
|
-
/**
|
|
13
|
-
* @deprecated Use `context.schema` instead
|
|
14
|
-
*/
|
|
15
|
-
schema: EditorContext['schema']
|
|
16
|
-
}) => string | undefined
|
|
17
|
-
}) {
|
|
18
|
-
return defineInputRule({
|
|
19
|
-
on: /^(-|\*) /,
|
|
20
|
-
guard: ({snapshot, event}) => {
|
|
21
|
-
const unorderedList = config.unorderedList({
|
|
22
|
-
context: {schema: snapshot.context.schema},
|
|
23
|
-
schema: snapshot.context.schema,
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
if (!unorderedList) {
|
|
27
|
-
return false
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const previousInlineObject = getPreviousInlineObject(snapshot)
|
|
31
|
-
|
|
32
|
-
if (previousInlineObject) {
|
|
33
|
-
return false
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const match = event.matches.at(0)
|
|
37
|
-
|
|
38
|
-
if (!match) {
|
|
39
|
-
return false
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return {match, unorderedList}
|
|
43
|
-
},
|
|
44
|
-
actions: [
|
|
45
|
-
({event}, {match, unorderedList}) => [
|
|
46
|
-
raise({
|
|
47
|
-
type: 'block.unset',
|
|
48
|
-
props: ['style'],
|
|
49
|
-
at: event.focusBlock.path,
|
|
50
|
-
}),
|
|
51
|
-
raise({
|
|
52
|
-
type: 'block.set',
|
|
53
|
-
props: {
|
|
54
|
-
listItem: unorderedList,
|
|
55
|
-
level: event.focusBlock.node.level ?? 1,
|
|
56
|
-
},
|
|
57
|
-
at: event.focusBlock.path,
|
|
58
|
-
}),
|
|
59
|
-
raise({
|
|
60
|
-
type: 'delete',
|
|
61
|
-
at: match.targetOffsets,
|
|
62
|
-
}),
|
|
63
|
-
],
|
|
64
|
-
],
|
|
65
|
-
})
|
|
66
|
-
}
|