Skip to content

Commit

Permalink
merge main into fix/react-flushsync
Browse files Browse the repository at this point in the history
  • Loading branch information
bdbch committed Feb 27, 2023
2 parents 880ac5d + acf186d commit 69db322
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 23 deletions.
Empty file.
11 changes: 11 additions & 0 deletions demos/src/Commands/InsertContent/Vue/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
context('/src/Commands/InsertContent/Vue/', () => {
before(() => {
cy.visit('/src/Commands/InsertContent/Vue/')
})

beforeEach(() => {
cy.get('.ProseMirror').then(([{ editor }]) => {
editor.commands.clearContent()
})
})
})
102 changes: 102 additions & 0 deletions demos/src/Commands/InsertContent/Vue/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
<template>
<div v-if="editor">
<button @click="insertContentByString">Insert content by string</button>
<button @click="insertContentByJSON">Insert content by JSON</button>
<button @click="insertTextByJSON">Insert text by JSON</button>
<button @click="insertTextByJSONArray">Insert text by JSON Array</button>
</div>
<editor-content :editor="editor" />
</template>

<script>
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import Text from '@tiptap/extension-text'
import { Editor, EditorContent } from '@tiptap/vue-3'
export default {
components: {
EditorContent,
},
data() {
return {
editor: null,
}
},
mounted() {
this.editor = new Editor({
extensions: [
Document,
Paragraph,
Text,
],
content: `
<p>
This is a radically reduced version of tiptap. It has support for a document, with paragraphs and text. That’s it. It’s probably too much for real minimalists though.
</p>
<p>
The paragraph extension is not really required, but you need at least one node. Sure, that node can be something different.
</p>
`,
})
},
beforeUnmount() {
this.editor.destroy()
},
methods: {
insertContentByString() {
this.editor.chain().focus().insertContent('<p>Hello World</p>').run()
},
insertContentByJSON() {
this.editor.chain().focus().insertContent({
type: 'paragraph',
content: [
{
type: 'text',
text: 'Hello',
},
{
type: 'text',
text: ' ',
},
{
type: 'text',
text: 'World',
},
],
}).run()
},
insertTextByJSON() {
this.editor.chain().focus().insertContent({
type: 'text',
text: 'Hello World',
}).run()
},
insertTextByJSONArray() {
this.editor.chain().focus().insertContent([{
type: 'text',
text: 'Hello',
}, {
type: 'text',
text: ' ',
}, {
type: 'text',
text: 'World',
}]).run()
},
},
}
</script>

<style lang="scss">
/* Basic editor styles */
.ProseMirror {
> * + * {
margin-top: 0.75em;
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ export default Node.create({
]
},

addKeyboardShortcuts() {
return {
'Mod-Enter': () => {
return this.editor.chain().insertContentAt(this.editor.state.selection.head, { type: this.type.name }).focus().run()
},
}
},

renderHTML({ HTMLAttributes }) {
return ['react-component', mergeAttributes(HTMLAttributes), 0]
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default () => {
This is still the text editor you’re used to, but enriched with node views.
</p>
<react-component>
<p>This is editable.</p>
<p>This is editable. You can create a new component by pressing Mod+Enter.</p>
</react-component>
<p>
Did you see that? That’s a React component. We are really living in the future.
Expand Down
12 changes: 12 additions & 0 deletions docs/api/marks/link.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ Link.configure({
})
```

By default, [linkify](https://linkify.js.org/docs/) adds `//` to the end of a protocol however this behavior can be changed by passing `optionalSlashes` option
```js
Link.configure({
protocols: [
{
scheme: 'tel',
optionalSlashes: true
}
]
})
```

### autolink
If enabled, it adds links as you type.

Expand Down
9 changes: 5 additions & 4 deletions docs/installation/php.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,13 @@ import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'

window.setupEditor = function (content) {
let editor

return {
editor: null,
content: content,

init(element) {
this.editor = new Editor({
editor = new Editor({
element: element,
extensions: [
StarterKit,
Expand All @@ -63,7 +64,7 @@ window.setupEditor = function (content) {

this.$watch('content', (content) => {
// If the new content matches TipTap's then we just skip.
if (content === this.editor.getHTML()) return
if (content === editor.getHTML()) return

/*
Otherwise, it means that a force external to TipTap
Expand All @@ -74,7 +75,7 @@ window.setupEditor = function (content) {
For more information on the `setContent()` method, see:
https://www.tiptap.dev/api/commands/set-content
*/
this.editor.commands.setContent(content, false)
editor.commands.setContent(content, false)
})
}
}
Expand Down
13 changes: 8 additions & 5 deletions package-lock.json

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

10 changes: 9 additions & 1 deletion packages/core/src/commands/insertContentAt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,15 @@ export const insertContentAt: RawCommands['insertContentAt'] = (position, value,
// if there is only plain text we have to use `insertText`
// because this will keep the current marks
if (isOnlyTextContent) {
tr.insertText(value as string, from, to)
// if value is string, we can use it directly
// otherwise if it is an array, we have to join it
if (Array.isArray(value)) {
tr.insertText(value.map(v => v.text || '').join(''), from, to)
} else if (typeof value === 'object' && !!value && !!value.text) {
tr.insertText(value.text, from, to)
} else {
tr.insertText(value as string, from, to)
}
} else {
tr.replaceWith(from, to, content)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/helpers/createNodeFromContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function createNodeFromContent(

if (typeof content === 'object' && content !== null) {
try {
if (Array.isArray(content)) {
if (Array.isArray(content) && content.length > 0) {
return Fragment.fromArray(content.map(item => schema.nodeFromJSON(item)))
}

Expand Down
2 changes: 1 addition & 1 deletion packages/extension-link/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"@tiptap/pm": "^2.0.0-beta.218"
},
"dependencies": {
"linkifyjs": "^3.0.5"
"linkifyjs": "^4.1.0"
},
"repository": {
"type": "git",
Expand Down
15 changes: 13 additions & 2 deletions packages/extension-link/src/link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import { autolink } from './helpers/autolink'
import { clickHandler } from './helpers/clickHandler'
import { pasteHandler } from './helpers/pasteHandler'

export interface LinkProtocolOptions {
scheme: string;
optionalSlashes?: boolean;
}

export interface LinkOptions {
/**
* If enabled, it adds links as you type.
Expand All @@ -14,7 +19,7 @@ export interface LinkOptions {
/**
* An array of custom protocols to be registered with linkifyjs.
*/
protocols: Array<string>
protocols: Array<LinkProtocolOptions | string>
/**
* If enabled, links will be opened on click.
*/
Expand Down Expand Up @@ -62,7 +67,13 @@ export const Link = Mark.create<LinkOptions>({
keepOnSplit: false,

onCreate() {
this.options.protocols.forEach(registerCustomProtocol)
this.options.protocols.forEach(protocol => {
if (typeof protocol === 'string') {
registerCustomProtocol(protocol)
return
}
registerCustomProtocol(protocol.scheme, protocol.optionalSlashes)
})
},

onDestroy() {
Expand Down
14 changes: 6 additions & 8 deletions packages/react/src/EditorContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,18 @@ const Portals: React.FC<{ renderers: Record<string, ReactRenderer> }> = ({ rende
return (
<>
{Object.entries(renderers).map(([key, renderer]) => {
return ReactDOM.createPortal(
renderer.reactElement,
renderer.element,
key,
)
return ReactDOM.createPortal(renderer.reactElement, renderer.element, key)
})}
</>
)
}

export interface EditorContentProps extends HTMLProps<HTMLDivElement> {
editor: Editor | null,
editor: Editor | null;
}

export interface EditorContentState {
renderers: Record<string, ReactRenderer>
renderers: Record<string, ReactRenderer>;
}

export class PureEditorContent extends React.Component<EditorContentProps, EditorContentState> {
Expand Down Expand Up @@ -79,7 +75,9 @@ export class PureEditorContent extends React.Component<EditorContentProps, Edito
// lifecycle methods, and React doesn't allow calling flushSync from inside
// a lifecycle method.
if (this.initialized) {
flushSync(fn)
queueMicrotask(() => {
flushSync(fn)
})
} else {
fn()
}
Expand Down

0 comments on commit 69db322

Please sign in to comment.