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:
Philipp Kühn
2021-10-26 18:31:13 +02:00
committed by GitHub
parent 2fff9c264b
commit 9afadeb7fe
57 changed files with 622 additions and 233 deletions

View File

@@ -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',

View File

@@ -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',

View File

@@ -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',

View File

@@ -29,8 +29,10 @@ export const Blockquote = Node.create<BlockquoteOptions>({
name: 'blockquote',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
content: 'block*',

View File

@@ -36,8 +36,10 @@ export const underscorePasteRegex = /(?:^|\s)((?:__)((?:[^__]+))(?:__))/g
export const Bold = Mark.create<BoldOptions>({
name: 'bold',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
parseHTML() {

View File

@@ -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() {

View File

@@ -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',

View File

@@ -10,8 +10,10 @@ export interface CharacterCountOptions {
export const CharacterCount = Extension.create<CharacterCountOptions>({
name: 'characterCount',
defaultOptions: {
limit: 0,
addOptions() {
return {
limit: 0,
}
},
addProseMirrorPlugins() {

View File

@@ -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() {

View File

@@ -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*',

View File

@@ -34,8 +34,10 @@ export const pasteRegex = /(?:^|\s)((?:`)((?:[^`]+))(?:`))/g
export const Code = Mark.create<CodeOptions>({
name: 'code',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
excludes: '_',

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -23,8 +23,10 @@ declare module '@tiptap/core' {
export const Color = Extension.create<ColorOptions>({
name: 'color',
defaultOptions: {
types: ['textStyle'],
addOptions() {
return {
types: ['textStyle'],
}
},
addGlobalAttributes() {

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -23,8 +23,10 @@ declare module '@tiptap/core' {
export const FontFamily = Extension.create<FontFamilyOptions>({
name: 'fontFamily',
defaultOptions: {
types: ['textStyle'],
addOptions() {
return {
types: ['textStyle'],
}
},
addGlobalAttributes() {

View File

@@ -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,

View File

@@ -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*',

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -23,8 +23,10 @@ declare module '@tiptap/core' {
export const HorizontalRule = Node.create<HorizontalRuleOptions>({
name: 'horizontalRule',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
group: 'block',

View File

@@ -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() {

View File

@@ -36,8 +36,10 @@ export const underscorePasteRegex = /(?:^|\s)((?:_)((?:[^_]+))(?:_))/g
export const Italic = Mark.create<ItalicOptions>({
name: 'italic',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
parseHTML() {

View File

@@ -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() {

View File

@@ -7,8 +7,10 @@ export interface ListItemOptions {
export const ListItem = Node.create<ListItemOptions>({
name: 'listItem',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
content: 'paragraph block*',

View File

@@ -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',

View File

@@ -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',

View File

@@ -20,8 +20,10 @@ export const Paragraph = Node.create<ParagraphOptions>({
priority: 1000,
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
group: 'block',

View File

@@ -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() {

View File

@@ -34,8 +34,10 @@ export const pasteRegex = /(?:^|\s)((?:~~)((?:[^~]+))(?:~~))/g
export const Strike = Mark.create<StrikeOptions>({
name: 'strike',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
parseHTML() {

View File

@@ -26,8 +26,10 @@ declare module '@tiptap/core' {
export const Subscript = Mark.create<SubscriptExtensionOptions>({
name: 'subscript',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
parseHTML() {

View File

@@ -26,8 +26,10 @@ declare module '@tiptap/core' {
export const Superscript = Mark.create<SuperscriptExtensionOptions>({
name: 'superscript',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
parseHTML() {

View File

@@ -7,8 +7,10 @@ export interface TableCellOptions {
export const TableCell = Node.create<TableCellOptions>({
name: 'tableCell',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
content: 'block+',

View File

@@ -6,8 +6,10 @@ export interface TableHeaderOptions {
export const TableHeader = Node.create<TableHeaderOptions>({
name: 'tableHeader',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
content: 'block+',

View File

@@ -7,8 +7,10 @@ export interface TableRowOptions {
export const TableRow = Node.create<TableRowOptions>({
name: 'tableRow',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
content: '(tableCell | tableHeader)*',

View File

@@ -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+',

View File

@@ -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() {

View File

@@ -18,8 +18,10 @@ declare module '@tiptap/core' {
export const TaskList = Node.create<TaskListOptions>({
name: 'taskList',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
group: 'block list',

View File

@@ -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() {

View File

@@ -22,8 +22,10 @@ declare module '@tiptap/core' {
export const TextStyle = Mark.create<TextStyleOptions>({
name: 'textStyle',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
parseHTML() {

View File

@@ -26,8 +26,10 @@ declare module '@tiptap/core' {
export const Underline = Mark.create<UnderlineOptions>({
name: 'underline',
defaultOptions: {
HTMLAttributes: {},
addOptions() {
return {
HTMLAttributes: {},
}
},
parseHTML() {