🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@tiptap/core

Package Overview
Dependencies
Maintainers
6
Versions
497
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@tiptap/core - npm Package Compare versions

Comparing version
3.26.1
to
3.27.0
+49
src/__tests__/rewriteUnknownContent.test.ts
import { Schema } from '@tiptap/pm/model'
import { describe, expect, it } from 'vitest'
import { rewriteUnknownContent } from '../helpers/rewriteUnknownContent.js'
const schema = new Schema({
nodes: {
doc: { content: 'block+' },
paragraph: { group: 'block', content: 'inline*' },
text: { group: 'inline' },
},
marks: { bold: {} },
})
describe('rewriteUnknownContent', () => {
it('drops nullish entries in a marks array without throwing', () => {
const json = {
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'hi', marks: [null] }] }],
}
const result = rewriteUnknownContent(json as any, schema)
expect(result.json).toEqual({
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'hi', marks: [] }] }],
})
})
it('drops nullish entries in a content array without throwing', () => {
const json = { type: 'doc', content: [null] }
const result = rewriteUnknownContent(json as any, schema)
expect(result.json).toEqual({ type: 'doc', content: [] })
})
it('still keeps valid marks and content', () => {
const json = {
type: 'doc',
content: [{ type: 'paragraph', content: [{ type: 'text', text: 'hi', marks: [{ type: 'bold' }] }] }],
}
const result = rewriteUnknownContent(json as any, schema)
expect(result.json).toEqual(json)
expect(result.rewrittenContent).toHaveLength(0)
})
})
+6
-3
{
"name": "@tiptap/core",
"version": "3.26.1",
"version": "3.27.0",
"description": "headless rich text editor",

@@ -19,2 +19,5 @@ "keywords": [

},
"bugs": {
"url": "https://github.com/ueberdosis/tiptap/issues"
},
"funding": {

@@ -62,6 +65,6 @@ "type": "github",

"devDependencies": {
"@tiptap/pm": "^3.26.1"
"@tiptap/pm": "^3.27.0"
},
"peerDependencies": {
"@tiptap/pm": "3.26.1"
"@tiptap/pm": "3.27.0"
},

@@ -68,0 +71,0 @@ "scripts": {

@@ -11,2 +11,21 @@ import type { NodeType } from '@tiptap/pm/model'

/**
* Normalise a list type attribute for comparison.
* Treats null, undefined, and "1" as equivalent (the default numeric type).
*/
function normalizeListType(type: string | null | undefined): string | null {
return !type || type === '1' ? null : type
}
/**
* Check if two list type attributes are compatible for joining.
* Lists can only join when they have the same type (both default, or both the same non-default type).
*/
function areListTypesCompatible(
typeA: string | null | undefined,
typeB: string | null | undefined,
): boolean {
return normalizeListType(typeA) === normalizeListType(typeB)
}
const joinListBackwards = (tr: Transaction, listType: NodeType): boolean => {

@@ -32,2 +51,8 @@ const list = findParentNode(node => node.type === listType)(tr.selection)

// Don't join if the type attributes are incompatible
// (e.g. a default-type list should not merge with a type="a" list)
if (!areListTypesCompatible(list.node.attrs.type, nodeBefore?.attrs.type)) {
return true
}
tr.join(list.pos)

@@ -58,2 +83,7 @@

// Don't join if the type attributes are incompatible
if (!areListTypesCompatible(list.node.attrs.type, nodeAfter?.attrs.type)) {
return true
}
tr.join(after)

@@ -60,0 +90,0 @@

@@ -11,2 +11,3 @@ import { keymap } from '@tiptap/pm/keymap'

getExtensionField,
getMarkType,
getNodeType,

@@ -21,14 +22,9 @@ getRenderedAttributes,

} from './helpers/index.js'
import {
type MarkConfig,
type NodeConfig,
type Storage,
getMarkType,
updateMarkViewAttributes,
} from './index.js'
import { updateMarkViewAttributes } from './MarkView.js'
import { inputRulesPlugin } from './InputRule.js'
import { Mark } from './Mark.js'
import { Mark, type MarkConfig } from './Mark.js'
import { pasteRulesPlugin } from './PasteRule.js'
import type { AnyConfig, Extensions, RawCommands } from './types.js'
import type { AnyConfig, Extensions, RawCommands, Storage } from './types.js'
import { callOrReturn } from './utilities/callOrReturn.js'
import { type NodeConfig } from './Node.js'

@@ -35,0 +31,0 @@ export class ExtensionManager {

@@ -51,2 +51,7 @@ import type { Schema } from '@tiptap/pm/model'

json.marks = json.marks.filter(mark => {
if (mark === null || mark === undefined) {
// Drop malformed (nullish) mark entries instead of crashing
return false
}
const name = typeof mark === 'string' ? mark : mark.type

@@ -69,12 +74,16 @@

json.content = json.content
.map(
value =>
rewriteUnknownContentInner({
json: value,
validMarks,
validNodes,
options,
rewrittenContent,
}).json,
)
.map(value => {
if (value === null || value === undefined) {
// Drop malformed (nullish) content entries instead of crashing
return null
}
return rewriteUnknownContentInner({
json: value,
validMarks,
validNodes,
options,
rewrittenContent,
}).json
})
.filter(a => a !== null && a !== undefined)

@@ -81,0 +90,0 @@ }

@@ -23,7 +23,1 @@ export * from './CommandManager.js'

export * from './utilities/index.js'
// oxlint-disable-next-line
export interface Commands<ReturnType = any> {}
// oxlint-disable-next-line
export interface Storage {}

@@ -23,12 +23,6 @@ import type {

import type { Extendable } from './Extendable.js'
import type {
Commands,
ExtensionConfig,
GetUpdatedPositionResult,
MappablePosition,
MarkConfig,
NodeConfig,
} from './index.js'
import type { Mark } from './Mark.js'
import type { Node } from './Node.js'
import type { ExtensionConfig } from './Extension.js'
import type { GetUpdatedPositionResult, MappablePosition } from './helpers/MappablePosition.js'
import type { Mark, MarkConfig } from './Mark.js'
import type { Node, NodeConfig } from './Node.js'

@@ -1108,1 +1102,6 @@ export type AnyConfig = ExtensionConfig | NodeConfig | MarkConfig

}
// oxlint-disable-next-line no-unused-vars
export interface Commands<ReturnType = any> {}
export interface Storage {}

@@ -41,3 +41,3 @@ /**

// Parse classes (.className) - only outside of quoted strings
const classMatches = tempString.match(/(?:^|\s)\.([a-zA-Z][\w-]*)/g)
const classMatches = tempString.match(/(?:^|\s)\.([\w-]+)/g)
if (classMatches) {

@@ -49,3 +49,3 @@ const classes = classMatches.map(match => match.trim().slice(1)) // Remove the dot

// Parse IDs (#myId) - only outside of quoted strings
const idMatch = tempString.match(/(?:^|\s)#([a-zA-Z][\w-]*)/)
const idMatch = tempString.match(/(?:^|\s)#([\w-]+)/)
if (idMatch) {

@@ -69,4 +69,4 @@ attributes.id = idMatch[1]

const cleanString = tempString
.replace(/(?:^|\s)\.([a-zA-Z][\w-]*)/g, '') // Remove classes
.replace(/(?:^|\s)#([a-zA-Z][\w-]*)/g, '') // Remove IDs
.replace(/(?:^|\s)\.([\w-]+)/g, '') // Remove classes
.replace(/(?:^|\s)#([\w-]+)/g, '') // Remove IDs
.replace(/([a-zA-Z][\w-]*)\s*=\s*__QUOTED_\d+__/g, '') // Remove key-value pairs

@@ -73,0 +73,0 @@ .trim()

import { attrsEqual } from './attrsEqual.js'
/**
* Compare two arrays of mark objects for equality.
* Marks are compared by type and attributes (using attrsEqual),
* so key ordering in attrs does not matter.
* Shape accepted by marksEqual, works with both JSONContent marks
* (`{ type: 'bold' }`) and ProseMirror Mark objects (`{ type: MarkType }`).
*/
export function marksEqual(
a: { type: string; attrs?: Record<string, any> }[],
b: { type: string; attrs?: Record<string, any> }[],
): boolean {
export type MarkLike = {
type: string | { name: string }
attrs?: Record<string, any> | null
}
function markTypeName(mark: MarkLike): string {
return typeof mark.type === 'string' ? mark.type : mark.type.name
}
/**
* Compare two arrays of mark objects for equality (order-insensitive).
* Marks are matched by type name and attributes (via attrsEqual),
* so key ordering in attrs does not matter, nor does mark array order.
*/
export function marksEqual(a: readonly MarkLike[], b: readonly MarkLike[]): boolean {
if (a.length !== b.length) {

@@ -16,6 +26,20 @@ return false

return a.every((mark, i) => {
const other = b[i]
return mark.type === other.type && attrsEqual(mark.attrs, other.attrs)
// Marks are matched by (type, attrs) identity, so greedy first-match works.
const consumed = Array.from({ length: b.length }, () => false)
return a.every(markA => {
const nameA = markTypeName(markA)
const idx = b.findIndex(
(markB, i) =>
!consumed[i] && nameA === markTypeName(markB) && attrsEqual(markA.attrs, markB.attrs),
)
if (idx === -1) {
return false
}
consumed[idx] = true
return true
})
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display