Skip to content

Commit

Permalink
Merge branch 'develop' into next
Browse files Browse the repository at this point in the history
  • Loading branch information
nperez0111 committed Nov 11, 2024
2 parents 90dfd46 + 2178118 commit f7453a3
Show file tree
Hide file tree
Showing 32 changed files with 502 additions and 84 deletions.
5 changes: 5 additions & 0 deletions .changeset/chatty-monkeys-hear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tiptap/extension-list-keymap": patch
---

Fix backspace behavior when selection is not collapsed
5 changes: 5 additions & 0 deletions .changeset/five-flowers-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tiptap/vue-3": patch
---

Fix editor destruction before transition end if editor is nested
5 changes: 5 additions & 0 deletions .changeset/five-mice-turn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tiptap/extension-bubble-menu": patch
---

Add `element: HTMLElement` to `shouldShow` options within the BubbleMenu options.
5 changes: 5 additions & 0 deletions .changeset/lemon-berries-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tiptap/core": patch
---

feat: add `once` to EventEmitters
5 changes: 5 additions & 0 deletions .changeset/mean-moose-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tiptap/vue-2": patch
---

Pin vue-ts-types to a working version for vue-2
5 changes: 5 additions & 0 deletions .changeset/polite-buttons-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tiptap/react": patch
---

React 19 is now allowed as a peer dep, we did not have to make any changes for React 19
6 changes: 6 additions & 0 deletions .changeset/shy-pigs-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tiptap/core": patch
"@tiptap/extension-hard-break": patch
---

Add Node `linebreakReplacement` support and enable on hard-break nodes
5 changes: 5 additions & 0 deletions .changeset/swift-keys-collect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@tiptap/core": patch
---

Improve handling of selections with `updateAttributes`. Should no longer modify parent nodes of the same type.
6 changes: 6 additions & 0 deletions .changeset/witty-olives-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@tiptap/extension-link": patch
"tiptap-demos": patch
---

The link extension's `validate` option now applies to both auto-linking and XSS mitigation. While, the new `shouldAutoLink` option is used to disable auto linking on an otherwise valid url.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ Before submitting a pull request:
- Check the codebase to ensure that your feature doesn't already exist.
- Check the pull requests to ensure that another person hasn't already submitted the feature or fix.

Before commiting:
Before committing:

- Make sure to run the tests and linter before committing your changes.
- If you are making changes to one of the packages, make sure to **always** include a [changeset](https://github.com/changesets/changesets) in your PR describing **what changed** with a **description** of the change. Those are responsible for changelog creation
Expand Down
2 changes: 1 addition & 1 deletion demos/src/Examples/Transition/Vue/Extension.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { mergeAttributes, Node } from '@tiptap/core'
import { VueNodeViewRenderer } from '@tiptap/vue-3'

import Component from './Component.vue'
import Component from './VueComponent.vue'

export default Node.create({
name: 'vueComponent',
Expand Down
36 changes: 36 additions & 0 deletions demos/src/Examples/Transition/Vue/ParentComponent.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<template>
<div>
<EditorContent :editor="editor" />
</div>
</template>

<script setup lang="ts">
import StarterKit from '@tiptap/starter-kit'
import { EditorContent, useEditor } from '@tiptap/vue-3'
import { ref } from 'vue'
import VueComponent from './Extension.js'
import type { TNote } from './types.js'
const note = ref<TNote>({
id: 'note-1',
content: `
<p>Some random note text</p>
<vue-component count="0"></vue-component>
`,
})
const editor = useEditor({
content: note.value.content,
editorProps: {
attributes: {
class: 'textarea',
},
},
extensions: [
StarterKit,
VueComponent,
],
})
</script>
18 changes: 11 additions & 7 deletions demos/src/Examples/Transition/Vue/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,30 @@ context('/src/Examples/Transition/Vue/', () => {
cy.visit('/src/Examples/Transition/Vue/')
})

it('should not have an active tiptap instance but a button', () => {
it('should have two buttons and no active tiptap instance', () => {
cy.get('.tiptap').should('not.exist')

cy.get('#toggle-editor').should('exist')
cy.get('#toggle-direct-editor').should('exist')
cy.get('#toggle-nested-editor').should('exist')
})

it('clicking the button should show the editor', () => {
cy.get('#toggle-editor').click()
it('clicking the buttons should show two editors', () => {
cy.get('#toggle-direct-editor').click()
cy.get('#toggle-nested-editor').click()

cy.get('.tiptap').should('exist')
cy.get('.tiptap').should('be.visible')
})

it('clicking the button again should hide the editor', () => {
cy.get('#toggle-editor').click()
it('clicking the buttons again should hide the editors', () => {
cy.get('#toggle-direct-editor').click()
cy.get('#toggle-nested-editor').click()

cy.get('.tiptap').should('exist')
cy.get('.tiptap').should('be.visible')

cy.get('#toggle-editor').click()
cy.get('#toggle-direct-editor').click()
cy.get('#toggle-nested-editor').click()

cy.get('.tiptap').should('not.exist')
})
Expand Down
46 changes: 38 additions & 8 deletions demos/src/Examples/Transition/Vue/index.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
<script setup lang="ts">
import StarterKit from '@tiptap/starter-kit'
import { EditorContent, useEditor } from '@tiptap/vue-3'
import { ref } from 'vue'
import VueComponent from './Extension.js'
import ParentComponent from './ParentComponent.vue'
import type { TNote } from './types.js'
/** Display editor in the same component */
const showDirectEditor = ref(false)
/** Display editor in a child component */
const showNestedEditor = ref(false)
const note = ref<TNote>({
id: 'note-1',
content: `
Expand All @@ -28,24 +34,43 @@ const editor = useEditor({
],
})
const showEditor = ref(false)
</script>

<template>
<!-- Transition with editor in the same component -->
<div>
<button
id="toggle-direct-editor"
type="button"
@click="showEditor = !showEditor"
style="margin-bottom: 1rem;"
id="toggle-editor"
@click="showDirectEditor = !showDirectEditor"
>
{{ showEditor ? 'Hide editor' : 'Show editor' }}
{{ showDirectEditor ? 'Hide direct editor' : 'Show direct editor' }}
</button>

<transition name="fade">
<div v-if="showEditor" class="tiptap-wrapper">
<editor-content :editor="editor" />
<div v-if="showDirectEditor" class="tiptap-wrapper">
<EditorContent :editor="editor" />
</div>
</transition>
</div>

<hr>

<!-- Transition with editor in a child component -->
<div>
<button
id="toggle-nested-editor"
type="button"
style="margin-bottom: 1rem;"
@click="showNestedEditor = !showNestedEditor"
>
{{ showNestedEditor ? 'Hide nested editor' : 'Show nested editor' }}
</button>

<transition name="fade">
<div v-if="showNestedEditor" class="tiptap-wrapper">
<ParentComponent />
</div>
</transition>
</div>
Expand All @@ -62,6 +87,11 @@ const showEditor = ref(false)
opacity: 0;
}
hr {
margin-top: 1rem;
margin-bottom: 1rem;
}
.tiptap-wrapper {
background-color: var(--purple-light);
border: 2px solid var(--purple);
Expand Down
55 changes: 55 additions & 0 deletions demos/src/Marks/Link/React/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,61 @@ export default () => {
openOnClick: false,
autolink: true,
defaultProtocol: 'https',
protocols: ['http', 'https'],
isAllowedUri: (url, ctx) => {
try {
// construct URL
const parsedUrl = url.includes(':') ? new URL(url) : new URL(`${ctx.defaultProtocol}://${url}`)

// use default validation
if (!ctx.defaultValidate(parsedUrl.href)) {
return false
}

// disallowed protocols
const disallowedProtocols = ['ftp', 'file', 'mailto']
const protocol = parsedUrl.protocol.replace(':', '')

if (disallowedProtocols.includes(protocol)) {
return false
}

// only allow protocols specified in ctx.protocols
const allowedProtocols = ctx.protocols.map(p => (typeof p === 'string' ? p : p.scheme))

if (!allowedProtocols.includes(protocol)) {
return false
}

// disallowed domains
const disallowedDomains = ['example-phishing.com', 'malicious-site.net']
const domain = parsedUrl.hostname

if (disallowedDomains.includes(domain)) {
return false
}

// all checks have passed
return true
} catch (error) {
return false
}
},
shouldAutoLink: url => {
try {
// construct URL
const parsedUrl = url.includes(':') ? new URL(url) : new URL(`https://${url}`)

// only auto-link if the domain is not in the disallowed list
const disallowedDomains = ['example-no-autolink.com', 'another-no-autolink.com']
const domain = parsedUrl.hostname

return !disallowedDomains.includes(domain)
} catch (error) {
return false
}
},

}),
],
content: `
Expand Down
42 changes: 35 additions & 7 deletions demos/src/Marks/Link/React/index.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,27 @@ context('/src/Marks/Link/React/', () => {

it('should parse a tags correctly', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p><a href="#">Example Text1</a></p>')
editor.commands.setContent('<p><a href="https://example.com">Example Text1</a></p>')
expect(editor.getHTML()).to.eq(
'<p><a target="_blank" rel="noopener noreferrer nofollow" href="#">Example Text1</a></p>',
'<p><a target="_blank" rel="noopener noreferrer nofollow" href="https://example.com">Example Text1</a></p>',
)
})
})

it('should parse a tags with target attribute correctly', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p><a href="#" target="_self">Example Text2</a></p>')
editor.commands.setContent('<p><a href="https://example.com" target="_self">Example Text2</a></p>')
expect(editor.getHTML()).to.eq(
'<p><a target="_self" rel="noopener noreferrer nofollow" href="#">Example Text2</a></p>',
'<p><a target="_self" rel="noopener noreferrer nofollow" href="https://example.com">Example Text2</a></p>',
)
})
})

it('should parse a tags with rel attribute correctly', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p><a href="#" rel="follow">Example Text3</a></p>')
editor.commands.setContent('<p><a href="https://example.com" rel="follow">Example Text3</a></p>')
expect(editor.getHTML()).to.eq(
'<p><a target="_blank" rel="follow" href="#">Example Text3</a></p>',
'<p><a target="_blank" rel="follow" href="https://example.com">Example Text3</a></p>',
)
})
})
Expand All @@ -54,7 +54,7 @@ context('/src/Marks/Link/React/', () => {

it('should allow exiting the link once set', () => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent('<p><a href="#" target="_self">Example Text2</a></p>')
editor.commands.setContent('<p><a href="https://example.com" target="_self">Example Text2</a></p>')
cy.get('.tiptap').type('{rightArrow}')

cy.get('button:first').should('not.have.class', 'is-active')
Expand Down Expand Up @@ -129,4 +129,32 @@ context('/src/Marks/Link/React/', () => {
.find('a[href="http://example3.com/foobar"]')
.should('contain', 'http://example3.com/foobar')
})

it('should not allow links with disallowed protocols', () => {
const disallowedProtocols = ['ftp://example.com', 'file:///example.txt', 'mailto:[email protected]']

disallowedProtocols.forEach(url => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent(`<p><a href="${url}">Example Text</a></p>`)
expect(editor.getHTML()).to.not.include(url)
})
})
})

it('should not allow links with disallowed domains', () => {
const disallowedDomains = ['https://example-phishing.com', 'https://malicious-site.net']

disallowedDomains.forEach(url => {
cy.get('.tiptap').then(([{ editor }]) => {
editor.commands.setContent(`<p><a href="${url}">Example Text</a></p>`)
expect(editor.getHTML()).to.not.include(url)
})
})
})

it('should not auto-link a URL from a disallowed domain', () => {
cy.get('.tiptap').type('https://example-phishing.com ') // disallowed domain
cy.get('.tiptap').should('not.have.descendants', 'a')
cy.get('.tiptap').should('contain.text', 'https://example-phishing.com')
})
})
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit f7453a3

Please sign in to comment.