mirror of
https://github.com/ueberdosis/tiptap.git
synced 2024-11-24 03:39:01 +08:00
feat!: Replace defaultOptions
with addOptions
(#2088)
* add new addOptions option * replace defaultOptions with addOptions for all extensions * replace defaultOptions with addOptions for all demos * replace defaultOptions with addOptions in docs * refactoring * refactoring * drop object support for addOptions * fix optional options * fix tests
This commit is contained in:
parent
2fff9c264b
commit
9afadeb7fe
@ -59,20 +59,22 @@ declare module '@tiptap/core' {
|
||||
}
|
||||
}
|
||||
|
||||
export const CollaborationAnnotation = Extension.create({
|
||||
export const CollaborationAnnotation = Extension.create<AnnotationOptions>({
|
||||
name: 'annotation',
|
||||
|
||||
priority: 1000,
|
||||
|
||||
defaultOptions: <AnnotationOptions>{
|
||||
HTMLAttributes: {
|
||||
class: 'annotation',
|
||||
},
|
||||
onUpdate: decorations => decorations,
|
||||
document: null,
|
||||
field: 'annotations',
|
||||
map: null,
|
||||
instance: '',
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {
|
||||
class: 'annotation',
|
||||
},
|
||||
onUpdate: decorations => decorations,
|
||||
document: null,
|
||||
field: 'annotations',
|
||||
map: null,
|
||||
instance: '',
|
||||
}
|
||||
},
|
||||
|
||||
onCreate() {
|
||||
|
@ -2,16 +2,17 @@ import { Extension } from '@tiptap/core'
|
||||
import Suggestion from '@tiptap/suggestion'
|
||||
|
||||
export default Extension.create({
|
||||
name: 'mention',
|
||||
name: 'commands',
|
||||
|
||||
defaultOptions: {
|
||||
suggestion: {
|
||||
char: '/',
|
||||
startOfLine: false,
|
||||
command: ({ editor, range, props }) => {
|
||||
props.command({ editor, range })
|
||||
addOptions() {
|
||||
return {
|
||||
suggestion: {
|
||||
char: '/',
|
||||
command: ({ editor, range, props }) => {
|
||||
props.command({ editor, range })
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
|
@ -17,8 +17,10 @@ export default Node.create<DetailsSummaryOptions>({
|
||||
|
||||
isolating: true,
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
@ -34,8 +34,10 @@ export default Node.create<DetailsOptions>({
|
||||
|
||||
// defining: true,
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
@ -18,18 +18,20 @@ declare module '@tiptap/core' {
|
||||
}
|
||||
}
|
||||
|
||||
export default Node.create({
|
||||
export default Node.create<IframeOptions>({
|
||||
name: 'iframe',
|
||||
|
||||
group: 'block',
|
||||
|
||||
atom: true,
|
||||
|
||||
defaultOptions: <IframeOptions>{
|
||||
allowFullscreen: true,
|
||||
HTMLAttributes: {
|
||||
class: 'iframe-wrapper',
|
||||
},
|
||||
addOptions() {
|
||||
return {
|
||||
allowFullscreen: true,
|
||||
HTMLAttributes: {
|
||||
class: 'iframe-wrapper',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
|
@ -41,8 +41,10 @@ export const inputRegex = /!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\)/
|
||||
export const Figure = Node.create<FigureOptions>({
|
||||
name: 'figure',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
group: 'block',
|
||||
|
@ -3,8 +3,10 @@ import { Node, mergeAttributes } from '@tiptap/core'
|
||||
export const Figcaption = Node.create({
|
||||
name: 'figcaption',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
content: 'inline*',
|
||||
|
@ -4,8 +4,10 @@ import { Plugin } from 'prosemirror-state'
|
||||
export const Figure = Node.create({
|
||||
name: 'figure',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
group: 'block',
|
||||
|
@ -39,11 +39,13 @@ export interface LinterOptions {
|
||||
plugins: Array<typeof LinterPlugin>,
|
||||
}
|
||||
|
||||
export const Linter = Extension.create({
|
||||
export const Linter = Extension.create<LinterOptions>({
|
||||
name: 'linter',
|
||||
|
||||
defaultOptions: <LinterOptions>{
|
||||
plugins: [],
|
||||
addOptions() {
|
||||
return {
|
||||
plugins: [],
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
|
@ -20,11 +20,13 @@ export interface TrailingNodeOptions {
|
||||
export const TrailingNode = Extension.create<TrailingNodeOptions>({
|
||||
name: 'trailingNode',
|
||||
|
||||
defaultOptions: {
|
||||
node: 'paragraph',
|
||||
notAfter: [
|
||||
'paragraph',
|
||||
],
|
||||
addOptions() {
|
||||
return {
|
||||
node: 'paragraph',
|
||||
notAfter: [
|
||||
'paragraph',
|
||||
],
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
|
@ -71,9 +71,11 @@ All settings can be configured through the extension anyway, but if you want to
|
||||
import Heading from '@tiptap/extension-heading'
|
||||
|
||||
const CustomHeading = Heading.extend({
|
||||
defaultOptions: {
|
||||
...Heading.options,
|
||||
levels: [1, 2, 3],
|
||||
addOptions() {
|
||||
return {
|
||||
...this.parent(),
|
||||
levels: [1, 2, 3],
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
@ -26,8 +26,10 @@ export interface CustomExtensionOptions {
|
||||
}
|
||||
|
||||
const CustomExtension = Extension.create<CustomExtensionOptions>({
|
||||
defaultOptions: {
|
||||
awesomeness: 100,
|
||||
addOptions() {
|
||||
return {
|
||||
awesomeness: 100,
|
||||
}
|
||||
},
|
||||
})
|
||||
```
|
||||
|
@ -103,7 +103,7 @@ import { Node } from '@tiptap/core'
|
||||
|
||||
const CustomExtension = Node.create({
|
||||
name: 'custom_extension',
|
||||
defaultOptions: {
|
||||
addOptions() {
|
||||
…
|
||||
},
|
||||
addAttributes() {
|
||||
|
@ -36,13 +36,21 @@ declare module '@tiptap/core' {
|
||||
*/
|
||||
defaultOptions?: Options,
|
||||
|
||||
/**
|
||||
* Default Options
|
||||
*/
|
||||
addOptions?: (this: {
|
||||
name: string,
|
||||
parent: Exclude<ParentConfig<ExtensionConfig<Options, Storage>>['addOptions'], undefined>,
|
||||
}) => Options,
|
||||
|
||||
/**
|
||||
* Default Storage
|
||||
*/
|
||||
addStorage?: (this: {
|
||||
name: string,
|
||||
options: Options,
|
||||
parent: ParentConfig<ExtensionConfig<Options, Storage>>['addGlobalAttributes'],
|
||||
parent: Exclude<ParentConfig<ExtensionConfig<Options, Storage>>['addStorage'], undefined>,
|
||||
}) => Storage,
|
||||
|
||||
/**
|
||||
@ -278,7 +286,24 @@ export class Extension<Options = any, Storage = any> {
|
||||
}
|
||||
|
||||
this.name = this.config.name
|
||||
|
||||
if (config.defaultOptions) {
|
||||
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`)
|
||||
}
|
||||
|
||||
// TODO: remove `addOptions` fallback
|
||||
this.options = this.config.defaultOptions
|
||||
|
||||
if (this.config.addOptions) {
|
||||
this.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>(
|
||||
this,
|
||||
'addOptions',
|
||||
{
|
||||
name: this.name,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
this.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
|
||||
this,
|
||||
'addStorage',
|
||||
@ -286,7 +311,7 @@ export class Extension<Options = any, Storage = any> {
|
||||
name: this.name,
|
||||
options: this.options,
|
||||
},
|
||||
))
|
||||
)) || {}
|
||||
}
|
||||
|
||||
static create<O = any, S = any>(config: Partial<ExtensionConfig<O, S>> = {}) {
|
||||
@ -323,10 +348,25 @@ export class Extension<Options = any, Storage = any> {
|
||||
? extendedConfig.name
|
||||
: extension.parent.name
|
||||
|
||||
if (extendedConfig.defaultOptions) {
|
||||
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`)
|
||||
}
|
||||
|
||||
// TODO: remove `addOptions` fallback
|
||||
extension.options = extendedConfig.defaultOptions
|
||||
? extendedConfig.defaultOptions
|
||||
: extension.parent.options
|
||||
|
||||
if (extendedConfig.addOptions) {
|
||||
extension.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>(
|
||||
extension,
|
||||
'addOptions',
|
||||
{
|
||||
name: extension.name,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
|
||||
extension,
|
||||
'addStorage',
|
||||
|
@ -42,13 +42,21 @@ declare module '@tiptap/core' {
|
||||
*/
|
||||
defaultOptions?: Options,
|
||||
|
||||
/**
|
||||
* Default Options
|
||||
*/
|
||||
addOptions?: (this: {
|
||||
name: string,
|
||||
parent: Exclude<ParentConfig<MarkConfig<Options, Storage>>['addOptions'], undefined>,
|
||||
}) => Options,
|
||||
|
||||
/**
|
||||
* Default Storage
|
||||
*/
|
||||
addStorage?: (this: {
|
||||
addStorage?: (this: {
|
||||
name: string,
|
||||
options: Options,
|
||||
parent: ParentConfig<MarkConfig<Options, Storage>>['addGlobalAttributes'],
|
||||
parent: Exclude<ParentConfig<MarkConfig<Options, Storage>>['addStorage'], undefined>,
|
||||
}) => Storage,
|
||||
|
||||
/**
|
||||
@ -392,7 +400,24 @@ export class Mark<Options = any, Storage = any> {
|
||||
}
|
||||
|
||||
this.name = this.config.name
|
||||
|
||||
if (config.defaultOptions) {
|
||||
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`)
|
||||
}
|
||||
|
||||
// TODO: remove `addOptions` fallback
|
||||
this.options = this.config.defaultOptions
|
||||
|
||||
if (this.config.addOptions) {
|
||||
this.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>(
|
||||
this,
|
||||
'addOptions',
|
||||
{
|
||||
name: this.name,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
this.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
|
||||
this,
|
||||
'addStorage',
|
||||
@ -400,7 +425,7 @@ export class Mark<Options = any, Storage = any> {
|
||||
name: this.name,
|
||||
options: this.options,
|
||||
},
|
||||
))
|
||||
)) || {}
|
||||
}
|
||||
|
||||
static create<O = any, S = any>(config: Partial<MarkConfig<O, S>> = {}) {
|
||||
@ -437,10 +462,25 @@ export class Mark<Options = any, Storage = any> {
|
||||
? extendedConfig.name
|
||||
: extension.parent.name
|
||||
|
||||
if (extendedConfig.defaultOptions) {
|
||||
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`)
|
||||
}
|
||||
|
||||
// TODO: remove `addOptions` fallback
|
||||
extension.options = extendedConfig.defaultOptions
|
||||
? extendedConfig.defaultOptions
|
||||
: extension.parent.options
|
||||
|
||||
if (extendedConfig.addOptions) {
|
||||
extension.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>(
|
||||
extension,
|
||||
'addOptions',
|
||||
{
|
||||
name: extension.name,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
|
||||
extension,
|
||||
'addStorage',
|
||||
|
@ -42,13 +42,21 @@ declare module '@tiptap/core' {
|
||||
*/
|
||||
defaultOptions?: Options,
|
||||
|
||||
/**
|
||||
* Default Options
|
||||
*/
|
||||
addOptions?: (this: {
|
||||
name: string,
|
||||
parent: Exclude<ParentConfig<NodeConfig<Options, Storage>>['addOptions'], undefined>,
|
||||
}) => Options,
|
||||
|
||||
/**
|
||||
* Default Storage
|
||||
*/
|
||||
addStorage?: (this: {
|
||||
name: string,
|
||||
options: Options,
|
||||
parent: ParentConfig<NodeConfig<Options, Storage>>['addGlobalAttributes'],
|
||||
parent: Exclude<ParentConfig<NodeConfig<Options, Storage>>['addStorage'], undefined>,
|
||||
}) => Storage,
|
||||
|
||||
/**
|
||||
@ -472,7 +480,24 @@ export class Node<Options = any, Storage = any> {
|
||||
}
|
||||
|
||||
this.name = this.config.name
|
||||
|
||||
if (config.defaultOptions) {
|
||||
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`)
|
||||
}
|
||||
|
||||
// TODO: remove `addOptions` fallback
|
||||
this.options = this.config.defaultOptions
|
||||
|
||||
if (this.config.addOptions) {
|
||||
this.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>(
|
||||
this,
|
||||
'addOptions',
|
||||
{
|
||||
name: this.name,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
this.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
|
||||
this,
|
||||
'addStorage',
|
||||
@ -480,7 +505,7 @@ export class Node<Options = any, Storage = any> {
|
||||
name: this.name,
|
||||
options: this.options,
|
||||
},
|
||||
))
|
||||
)) || {}
|
||||
}
|
||||
|
||||
static create<O = any, S = any>(config: Partial<NodeConfig<O, S>> = {}) {
|
||||
@ -517,10 +542,25 @@ export class Node<Options = any, Storage = any> {
|
||||
? extendedConfig.name
|
||||
: extension.parent.name
|
||||
|
||||
if (extendedConfig.defaultOptions) {
|
||||
console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`)
|
||||
}
|
||||
|
||||
// TODO: remove `addOptions` fallback
|
||||
extension.options = extendedConfig.defaultOptions
|
||||
? extendedConfig.defaultOptions
|
||||
: extension.parent.options
|
||||
|
||||
if (extendedConfig.addOptions) {
|
||||
extension.options = callOrReturn(getExtensionField<AnyConfig['addOptions']>(
|
||||
extension,
|
||||
'addOptions',
|
||||
{
|
||||
name: extension.name,
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
extension.storage = callOrReturn(getExtensionField<AnyConfig['addStorage']>(
|
||||
extension,
|
||||
'addStorage',
|
||||
|
@ -29,8 +29,10 @@ export const Blockquote = Node.create<BlockquoteOptions>({
|
||||
|
||||
name: 'blockquote',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
content: 'block*',
|
||||
|
@ -36,8 +36,10 @@ export const underscorePasteRegex = /(?:^|\s)((?:__)((?:[^__]+))(?:__))/g
|
||||
export const Bold = Mark.create<BoldOptions>({
|
||||
name: 'bold',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
@ -8,11 +8,13 @@ export type BubbleMenuOptions = Omit<BubbleMenuPluginProps, 'editor' | 'element'
|
||||
export const BubbleMenu = Extension.create<BubbleMenuOptions>({
|
||||
name: 'bubbleMenu',
|
||||
|
||||
defaultOptions: {
|
||||
element: null,
|
||||
tippyOptions: {},
|
||||
pluginKey: 'bubbleMenu',
|
||||
shouldShow: null,
|
||||
addOptions() {
|
||||
return {
|
||||
element: null,
|
||||
tippyOptions: {},
|
||||
pluginKey: 'bubbleMenu',
|
||||
shouldShow: null,
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
|
@ -20,8 +20,10 @@ export const inputRegex = /^\s*([-+*])\s$/
|
||||
export const BulletList = Node.create<BulletListOptions>({
|
||||
name: 'bulletList',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
group: 'block list',
|
||||
|
@ -10,8 +10,10 @@ export interface CharacterCountOptions {
|
||||
export const CharacterCount = Extension.create<CharacterCountOptions>({
|
||||
name: 'characterCount',
|
||||
|
||||
defaultOptions: {
|
||||
limit: 0,
|
||||
addOptions() {
|
||||
return {
|
||||
limit: 0,
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
|
@ -7,9 +7,11 @@ export interface CodeBlockLowlightOptions extends CodeBlockOptions {
|
||||
}
|
||||
|
||||
export const CodeBlockLowlight = CodeBlock.extend<CodeBlockLowlightOptions>({
|
||||
defaultOptions: {
|
||||
...CodeBlock.options,
|
||||
lowlight,
|
||||
addOptions() {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
lowlight,
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
|
@ -27,9 +27,11 @@ export const tildeInputRegex = /^~~~(?<language>[a-z]*)?[\s\n]$/
|
||||
export const CodeBlock = Node.create<CodeBlockOptions>({
|
||||
name: 'codeBlock',
|
||||
|
||||
defaultOptions: {
|
||||
languageClassPrefix: 'language-',
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
languageClassPrefix: 'language-',
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
content: 'text*',
|
||||
|
@ -34,8 +34,10 @@ export const pasteRegex = /(?:^|\s)((?:`)((?:[^`]+))(?:`))/g
|
||||
export const Code = Mark.create<CodeOptions>({
|
||||
name: 'code',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
excludes: '_',
|
||||
|
@ -31,26 +31,28 @@ const awarenessStatesToArray = (states: Map<number, Record<string, any>>) => {
|
||||
export const CollaborationCursor = Extension.create<CollaborationCursorOptions>({
|
||||
name: 'collaborationCursor',
|
||||
|
||||
defaultOptions: {
|
||||
provider: null,
|
||||
user: {
|
||||
name: null,
|
||||
color: null,
|
||||
},
|
||||
render: user => {
|
||||
const cursor = document.createElement('span')
|
||||
cursor.classList.add('collaboration-cursor__caret')
|
||||
cursor.setAttribute('style', `border-color: ${user.color}`)
|
||||
addOptions() {
|
||||
return {
|
||||
provider: null,
|
||||
user: {
|
||||
name: null,
|
||||
color: null,
|
||||
},
|
||||
render: user => {
|
||||
const cursor = document.createElement('span')
|
||||
cursor.classList.add('collaboration-cursor__caret')
|
||||
cursor.setAttribute('style', `border-color: ${user.color}`)
|
||||
|
||||
const label = document.createElement('div')
|
||||
label.classList.add('collaboration-cursor__label')
|
||||
label.setAttribute('style', `background-color: ${user.color}`)
|
||||
label.insertBefore(document.createTextNode(user.name), null)
|
||||
cursor.insertBefore(label, null)
|
||||
const label = document.createElement('div')
|
||||
label.classList.add('collaboration-cursor__label')
|
||||
label.setAttribute('style', `background-color: ${user.color}`)
|
||||
label.insertBefore(document.createTextNode(user.name), null)
|
||||
cursor.insertBefore(label, null)
|
||||
|
||||
return cursor
|
||||
},
|
||||
onUpdate: () => null,
|
||||
return cursor
|
||||
},
|
||||
onUpdate: () => null,
|
||||
}
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -43,10 +43,12 @@ export const Collaboration = Extension.create<CollaborationOptions>({
|
||||
|
||||
priority: 1000,
|
||||
|
||||
defaultOptions: {
|
||||
document: null,
|
||||
field: 'default',
|
||||
fragment: null,
|
||||
addOptions() {
|
||||
return {
|
||||
document: null,
|
||||
field: 'default',
|
||||
fragment: null,
|
||||
}
|
||||
},
|
||||
|
||||
onCreate() {
|
||||
|
@ -23,8 +23,10 @@ declare module '@tiptap/core' {
|
||||
export const Color = Extension.create<ColorOptions>({
|
||||
name: 'color',
|
||||
|
||||
defaultOptions: {
|
||||
types: ['textStyle'],
|
||||
addOptions() {
|
||||
return {
|
||||
types: ['textStyle'],
|
||||
}
|
||||
},
|
||||
|
||||
addGlobalAttributes() {
|
||||
|
@ -10,10 +10,12 @@ export interface DropcursorOptions {
|
||||
export const Dropcursor = Extension.create<DropcursorOptions>({
|
||||
name: 'dropCursor',
|
||||
|
||||
defaultOptions: {
|
||||
color: 'currentColor',
|
||||
width: 1,
|
||||
class: null,
|
||||
addOptions() {
|
||||
return {
|
||||
color: 'currentColor',
|
||||
width: 1,
|
||||
class: null,
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
|
@ -8,11 +8,13 @@ export type FloatingMenuOptions = Omit<FloatingMenuPluginProps, 'editor' | 'elem
|
||||
export const FloatingMenu = Extension.create<FloatingMenuOptions>({
|
||||
name: 'floatingMenu',
|
||||
|
||||
defaultOptions: {
|
||||
element: null,
|
||||
tippyOptions: {},
|
||||
pluginKey: 'floatingMenu',
|
||||
shouldShow: null,
|
||||
addOptions() {
|
||||
return {
|
||||
element: null,
|
||||
tippyOptions: {},
|
||||
pluginKey: 'floatingMenu',
|
||||
shouldShow: null,
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
|
@ -10,9 +10,11 @@ export interface FocusOptions {
|
||||
export const FocusClasses = Extension.create<FocusOptions>({
|
||||
name: 'focus',
|
||||
|
||||
defaultOptions: {
|
||||
className: 'has-focus',
|
||||
mode: 'all',
|
||||
addOptions() {
|
||||
return {
|
||||
className: 'has-focus',
|
||||
mode: 'all',
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
|
@ -23,8 +23,10 @@ declare module '@tiptap/core' {
|
||||
export const FontFamily = Extension.create<FontFamilyOptions>({
|
||||
name: 'fontFamily',
|
||||
|
||||
defaultOptions: {
|
||||
types: ['textStyle'],
|
||||
addOptions() {
|
||||
return {
|
||||
types: ['textStyle'],
|
||||
}
|
||||
},
|
||||
|
||||
addGlobalAttributes() {
|
||||
|
@ -19,9 +19,11 @@ declare module '@tiptap/core' {
|
||||
export const HardBreak = Node.create<HardBreakOptions>({
|
||||
name: 'hardBreak',
|
||||
|
||||
defaultOptions: {
|
||||
keepMarks: true,
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
keepMarks: true,
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
inline: true,
|
||||
|
@ -25,9 +25,11 @@ declare module '@tiptap/core' {
|
||||
export const Heading = Node.create<HeadingOptions>({
|
||||
name: 'heading',
|
||||
|
||||
defaultOptions: {
|
||||
levels: [1, 2, 3, 4, 5, 6],
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
levels: [1, 2, 3, 4, 5, 6],
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
content: 'inline*',
|
||||
|
@ -35,9 +35,11 @@ export const pasteRegex = /(?:^|\s)((?:==)((?:[^~]+))(?:==))/g
|
||||
export const Highlight = Mark.create<HighlightOptions>({
|
||||
name: 'highlight',
|
||||
|
||||
defaultOptions: {
|
||||
multicolor: false,
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
multicolor: false,
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
|
@ -24,9 +24,11 @@ declare module '@tiptap/core' {
|
||||
export const History = Extension.create<HistoryOptions>({
|
||||
name: 'history',
|
||||
|
||||
defaultOptions: {
|
||||
depth: 100,
|
||||
newGroupDelay: 500,
|
||||
addOptions() {
|
||||
return {
|
||||
depth: 100,
|
||||
newGroupDelay: 500,
|
||||
}
|
||||
},
|
||||
|
||||
addCommands() {
|
||||
|
@ -23,8 +23,10 @@ declare module '@tiptap/core' {
|
||||
export const HorizontalRule = Node.create<HorizontalRuleOptions>({
|
||||
name: 'horizontalRule',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
group: 'block',
|
||||
|
@ -25,9 +25,11 @@ export const inputRegex = /(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))/
|
||||
export const Image = Node.create<ImageOptions>({
|
||||
name: 'image',
|
||||
|
||||
defaultOptions: {
|
||||
inline: false,
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
inline: false,
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
inline() {
|
||||
|
@ -36,8 +36,10 @@ export const underscorePasteRegex = /(?:^|\s)((?:_)((?:[^_]+))(?:_))/g
|
||||
export const Italic = Mark.create<ItalicOptions>({
|
||||
name: 'italic',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
@ -47,13 +47,15 @@ export const Link = Mark.create<LinkOptions>({
|
||||
|
||||
inclusive: false,
|
||||
|
||||
defaultOptions: {
|
||||
openOnClick: true,
|
||||
linkOnPaste: true,
|
||||
HTMLAttributes: {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer nofollow',
|
||||
},
|
||||
addOptions() {
|
||||
return {
|
||||
openOnClick: true,
|
||||
linkOnPaste: true,
|
||||
HTMLAttributes: {
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer nofollow',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
addAttributes() {
|
||||
|
@ -7,8 +7,10 @@ export interface ListItemOptions {
|
||||
export const ListItem = Node.create<ListItemOptions>({
|
||||
name: 'listItem',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
content: 'paragraph block*',
|
||||
|
@ -17,47 +17,49 @@ export const MentionPluginKey = new PluginKey('mention')
|
||||
export const Mention = Node.create<MentionOptions>({
|
||||
name: 'mention',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
renderLabel({ options, node }) {
|
||||
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
|
||||
},
|
||||
suggestion: {
|
||||
char: '@',
|
||||
pluginKey: MentionPluginKey,
|
||||
command: ({ editor, range, props }) => {
|
||||
// increase range.to by one when the next node is of type "text"
|
||||
// and starts with a space character
|
||||
const nodeAfter = editor.view.state.selection.$to.nodeAfter
|
||||
const overrideSpace = nodeAfter?.text?.startsWith(' ')
|
||||
|
||||
if (overrideSpace) {
|
||||
range.to += 1
|
||||
}
|
||||
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContentAt(range, [
|
||||
{
|
||||
type: 'mention',
|
||||
attrs: props,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' ',
|
||||
},
|
||||
])
|
||||
.run()
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
renderLabel({ options, node }) {
|
||||
return `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`
|
||||
},
|
||||
allow: ({ editor, range }) => {
|
||||
const $from = editor.state.doc.resolve(range.from)
|
||||
const type = editor.schema.nodes.mention
|
||||
const allow = !!$from.parent.type.contentMatch.matchType(type)
|
||||
suggestion: {
|
||||
char: '@',
|
||||
pluginKey: MentionPluginKey,
|
||||
command: ({ editor, range, props }) => {
|
||||
// increase range.to by one when the next node is of type "text"
|
||||
// and starts with a space character
|
||||
const nodeAfter = editor.view.state.selection.$to.nodeAfter
|
||||
const overrideSpace = nodeAfter?.text?.startsWith(' ')
|
||||
|
||||
return allow
|
||||
if (overrideSpace) {
|
||||
range.to += 1
|
||||
}
|
||||
|
||||
editor
|
||||
.chain()
|
||||
.focus()
|
||||
.insertContentAt(range, [
|
||||
{
|
||||
type: this.name,
|
||||
attrs: props,
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' ',
|
||||
},
|
||||
])
|
||||
.run()
|
||||
},
|
||||
allow: ({ editor, range }) => {
|
||||
const $from = editor.state.doc.resolve(range.from)
|
||||
const type = editor.schema.nodes[this.name]
|
||||
const allow = !!$from.parent.type.contentMatch.matchType(type)
|
||||
|
||||
return allow
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
group: 'inline',
|
||||
|
@ -20,8 +20,10 @@ export const inputRegex = /^(\d+)\.\s$/
|
||||
export const OrderedList = Node.create<OrderedListOptions>({
|
||||
name: 'orderedList',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
group: 'block list',
|
||||
|
@ -20,8 +20,10 @@ export const Paragraph = Node.create<ParagraphOptions>({
|
||||
|
||||
priority: 1000,
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
group: 'block',
|
||||
|
@ -19,13 +19,15 @@ export interface PlaceholderOptions {
|
||||
export const Placeholder = Extension.create<PlaceholderOptions>({
|
||||
name: 'placeholder',
|
||||
|
||||
defaultOptions: {
|
||||
emptyEditorClass: 'is-editor-empty',
|
||||
emptyNodeClass: 'is-empty',
|
||||
placeholder: 'Write something …',
|
||||
showOnlyWhenEditable: true,
|
||||
showOnlyCurrent: true,
|
||||
includeChildren: false,
|
||||
addOptions() {
|
||||
return {
|
||||
emptyEditorClass: 'is-editor-empty',
|
||||
emptyNodeClass: 'is-empty',
|
||||
placeholder: 'Write something …',
|
||||
showOnlyWhenEditable: true,
|
||||
showOnlyCurrent: true,
|
||||
includeChildren: false,
|
||||
}
|
||||
},
|
||||
|
||||
addProseMirrorPlugins() {
|
||||
|
@ -34,8 +34,10 @@ export const pasteRegex = /(?:^|\s)((?:~~)((?:[^~]+))(?:~~))/g
|
||||
export const Strike = Mark.create<StrikeOptions>({
|
||||
name: 'strike',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
@ -26,8 +26,10 @@ declare module '@tiptap/core' {
|
||||
export const Subscript = Mark.create<SubscriptExtensionOptions>({
|
||||
name: 'subscript',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
@ -26,8 +26,10 @@ declare module '@tiptap/core' {
|
||||
export const Superscript = Mark.create<SuperscriptExtensionOptions>({
|
||||
name: 'superscript',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
@ -7,8 +7,10 @@ export interface TableCellOptions {
|
||||
export const TableCell = Node.create<TableCellOptions>({
|
||||
name: 'tableCell',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
content: 'block+',
|
||||
|
@ -6,8 +6,10 @@ export interface TableHeaderOptions {
|
||||
export const TableHeader = Node.create<TableHeaderOptions>({
|
||||
name: 'tableHeader',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
content: 'block+',
|
||||
|
@ -7,8 +7,10 @@ export interface TableRowOptions {
|
||||
export const TableRow = Node.create<TableRowOptions>({
|
||||
name: 'tableRow',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
content: '(tableCell | tableHeader)*',
|
||||
|
@ -82,16 +82,18 @@ declare module '@tiptap/core' {
|
||||
export const Table = Node.create<TableOptions>({
|
||||
name: 'table',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
resizable: false,
|
||||
handleWidth: 5,
|
||||
cellMinWidth: 25,
|
||||
// TODO: fix
|
||||
// @ts-ignore
|
||||
View: TableView,
|
||||
lastColumnResizable: true,
|
||||
allowTableNodeSelection: false,
|
||||
// @ts-ignore
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
resizable: false,
|
||||
handleWidth: 5,
|
||||
cellMinWidth: 25,
|
||||
// TODO: fix
|
||||
View: TableView,
|
||||
lastColumnResizable: true,
|
||||
allowTableNodeSelection: false,
|
||||
}
|
||||
},
|
||||
|
||||
content: 'tableRow+',
|
||||
|
@ -10,9 +10,11 @@ export const inputRegex = /^\s*(\[([ |x])\])\s$/
|
||||
export const TaskItem = Node.create<TaskItemOptions>({
|
||||
name: 'taskItem',
|
||||
|
||||
defaultOptions: {
|
||||
nested: false,
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
nested: false,
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
content() {
|
||||
|
@ -18,8 +18,10 @@ declare module '@tiptap/core' {
|
||||
export const TaskList = Node.create<TaskListOptions>({
|
||||
name: 'taskList',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
group: 'block list',
|
||||
|
@ -24,10 +24,12 @@ declare module '@tiptap/core' {
|
||||
export const TextAlign = Extension.create<TextAlignOptions>({
|
||||
name: 'textAlign',
|
||||
|
||||
defaultOptions: {
|
||||
types: [],
|
||||
alignments: ['left', 'center', 'right', 'justify'],
|
||||
defaultAlignment: 'left',
|
||||
addOptions() {
|
||||
return {
|
||||
types: [],
|
||||
alignments: ['left', 'center', 'right', 'justify'],
|
||||
defaultAlignment: 'left',
|
||||
}
|
||||
},
|
||||
|
||||
addGlobalAttributes() {
|
||||
|
@ -22,8 +22,10 @@ declare module '@tiptap/core' {
|
||||
export const TextStyle = Mark.create<TextStyleOptions>({
|
||||
name: 'textStyle',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
@ -26,8 +26,10 @@ declare module '@tiptap/core' {
|
||||
export const Underline = Mark.create<UnderlineOptions>({
|
||||
name: 'underline',
|
||||
|
||||
defaultOptions: {
|
||||
HTMLAttributes: {},
|
||||
addOptions() {
|
||||
return {
|
||||
HTMLAttributes: {},
|
||||
}
|
||||
},
|
||||
|
||||
parseHTML() {
|
||||
|
@ -3,7 +3,7 @@
|
||||
import { Extension } from '@tiptap/core/src/Extension'
|
||||
|
||||
describe('extension options', () => {
|
||||
it('should set options', () => {
|
||||
it('should set options (deprecated)', () => {
|
||||
const extension = Extension.create({
|
||||
defaultOptions: {
|
||||
foo: 1,
|
||||
@ -17,7 +17,23 @@ describe('extension options', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass through', () => {
|
||||
it('should set options', () => {
|
||||
const extension = Extension.create({
|
||||
addOptions() {
|
||||
return {
|
||||
foo: 1,
|
||||
bar: 1,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
expect(extension.options).to.deep.eq({
|
||||
foo: 1,
|
||||
bar: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass through (deprecated)', () => {
|
||||
const extension = Extension
|
||||
.create({
|
||||
defaultOptions: {
|
||||
@ -34,7 +50,26 @@ describe('extension options', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should be configurable', () => {
|
||||
it('should pass through', () => {
|
||||
const extension = Extension
|
||||
.create({
|
||||
addOptions() {
|
||||
return {
|
||||
foo: 1,
|
||||
bar: 1,
|
||||
}
|
||||
},
|
||||
})
|
||||
.extend()
|
||||
.configure()
|
||||
|
||||
expect(extension.options).to.deep.eq({
|
||||
foo: 1,
|
||||
bar: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('should be configurable (deprecated)', () => {
|
||||
const extension = Extension
|
||||
.create({
|
||||
defaultOptions: {
|
||||
@ -52,7 +87,27 @@ describe('extension options', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should be extendable', () => {
|
||||
it('should be configurable', () => {
|
||||
const extension = Extension
|
||||
.create({
|
||||
addOptions() {
|
||||
return {
|
||||
foo: 1,
|
||||
bar: 1,
|
||||
}
|
||||
},
|
||||
})
|
||||
.configure({
|
||||
bar: 2,
|
||||
})
|
||||
|
||||
expect(extension.options).to.deep.eq({
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
})
|
||||
})
|
||||
|
||||
it('should be extendable (deprecated)', () => {
|
||||
const extension = Extension.create({
|
||||
defaultOptions: {
|
||||
foo: 1,
|
||||
@ -74,7 +129,33 @@ describe('extension options', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should be overwritable', () => {
|
||||
it('should be extendable', () => {
|
||||
const extension = Extension.create({
|
||||
addOptions() {
|
||||
return {
|
||||
foo: 1,
|
||||
bar: 1,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const newExtension = extension.extend({
|
||||
addOptions() {
|
||||
return {
|
||||
...this.parent?.(),
|
||||
baz: 1,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
expect(newExtension.options).to.deep.eq({
|
||||
foo: 1,
|
||||
bar: 1,
|
||||
baz: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('should be overwritable (deprecated)', () => {
|
||||
const extension = Extension
|
||||
.create({
|
||||
defaultOptions: {
|
||||
@ -93,7 +174,30 @@ describe('extension options', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should configure nested objects', () => {
|
||||
it('should be overwritable', () => {
|
||||
const extension = Extension
|
||||
.create({
|
||||
addOptions() {
|
||||
return {
|
||||
foo: 1,
|
||||
bar: 1,
|
||||
}
|
||||
},
|
||||
})
|
||||
.extend({
|
||||
addOptions() {
|
||||
return {
|
||||
baz: 1,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
expect(extension.options).to.deep.eq({
|
||||
baz: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it('should configure nested objects (deprecated)', () => {
|
||||
const extension = Extension
|
||||
.create<{
|
||||
foo: number[],
|
||||
@ -122,7 +226,38 @@ describe('extension options', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should create its own instance on configure', () => {
|
||||
it('should configure nested objects', () => {
|
||||
const extension = Extension
|
||||
.create<{
|
||||
foo: number[],
|
||||
HTMLAttributes: Record<string, any>,
|
||||
}>({
|
||||
addOptions() {
|
||||
return {
|
||||
foo: [1, 2, 3],
|
||||
HTMLAttributes: {
|
||||
class: 'foo',
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
.configure({
|
||||
foo: [1],
|
||||
HTMLAttributes: {
|
||||
id: 'bar',
|
||||
},
|
||||
})
|
||||
|
||||
expect(extension.options).to.deep.eq({
|
||||
foo: [1],
|
||||
HTMLAttributes: {
|
||||
class: 'foo',
|
||||
id: 'bar',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('should create its own instance on configure (deprecated)', () => {
|
||||
const extension = Extension
|
||||
.create({
|
||||
defaultOptions: {
|
||||
@ -150,4 +285,35 @@ describe('extension options', () => {
|
||||
bar: 2,
|
||||
})
|
||||
})
|
||||
|
||||
it('should create its own instance on configure', () => {
|
||||
const extension = Extension
|
||||
.create({
|
||||
addOptions() {
|
||||
return {
|
||||
foo: 1,
|
||||
bar: 2,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const extension1 = extension.configure({
|
||||
foo: 2,
|
||||
bar: 4,
|
||||
})
|
||||
|
||||
const extension2 = extension.configure({
|
||||
foo: 3,
|
||||
})
|
||||
|
||||
expect(extension1.options).to.deep.eq({
|
||||
foo: 2,
|
||||
bar: 4,
|
||||
})
|
||||
|
||||
expect(extension2.options).to.deep.eq({
|
||||
foo: 3,
|
||||
bar: 2,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user