Closed reefki closed 1 year ago
Hi, I think this is an Alpine issue. Downgrading to Alpine 2.8.2 works. It might have something to do with the fact that this.editor
is a Proxy object - though it's just a theory.
I don't know if the getUnobservedData()
still works, but you could try to use it to get access to the actual editor object. https://codewithhugo.com/alpinejs-inspect-component-data-from-js/
Hope this info helps :smile:
Thanks but getUnobservedData()
is no longer available.
@reefki did you find a way?
It appears to be definitely a proxy issue. I sort of got something working here by storing the editor object in a closure. https://codesandbox.io/s/tiptap-v2-alpinejs-v3-forked-6qxfs?file=/index.js
I don't know why reactivity is not working but it's a start and maybe this can point you in the right direction.
@jelib3an Thanks a lot.
If you add Alpine to the window scope you can use Alpine.raw(editor)
to unwrap the proxy and it should work.
@KevinBatdorf apologies with pinging you, can you explain your solution more? I'm a bit of a novice when it comes down further into the technical stuff and would really like to understand how to alleviate this issue with your solution. Thanks!
@KevinBatdorf Thanks for the suggestion! I never knew about Alpine.raw()
@robertdrakedennis Here's an example with the mentioned solution. https://codesandbox.io/s/tiptap-v2-alpinejs-v3-forked-2bbw8?file=/index.js
However, Alpine.raw()
doesn't appear to be bindable for reactivity. I also am Alpine novice. Would be interested to know if anyone has any good suggestions as it would be kind of a hassle to create a separate attribute for every button we want to check activeness on.
Just wanted to say that I have no experience with Alpine, but I’d love to update the docs when we found a solution. ✌️ Thanks for everyone helping out.
Using the demos above as a reference, instead of using this.editor
, just use window.editor = new Editor
(or name it however you like) and access it the same way. There's no need to make it a reactive property, and that seems to be what's breaking things here.
That's probably the more logical approach than my previous suggestion to unwrap the reactive parts on demand (with Alpine.raw()
).
Wanted to share my solution for having the editor not reactive, but still have a reactive menu for Tiptap/Alpine v3: https://codesandbox.io/s/tiptap-with-alpine-js-v3-q4qbp
Thanks @EasterPeanut your codesandbox and also phoenix project were very helpful in migrating to Alpine 3!
Yes thanks @EasterPeanut it helped ! Would be nice to have a solution without changing the variable scope like in VueJs and other though…
Thanks @EasterPeanut your solution is very helpful. But since I need to have multiple editor instances, so I ended up with this implementation:
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
export default (content) => {
let editors = window.tiptapEditors || {}
return {
id: null,
content: content,
updatedAt: Date.now(),
init() {
this.id = this.$el.getAttribute('id')
editors[this.id] = new Editor({
element: this.$refs.element,
extensions: [
StarterKit,
],
content: this.content,
onUpdate: ({ editor }) => {
this.content = editor.getHTML()
},
onSelectionUpdate: () => {
this.updatedAt = Date.now()
},
})
window.tiptapEditors = editors
},
editor() {
return editors[this.id]
},
toggleHeading(level) {
this.editor().chain().toggleHeading({ level }).focus().run()
},
toggleBold() {
this.editor().chain().toggleBold().focus().run()
},
toggleItalic() {
this.editor().chain().toggleItalic().focus().run()
},
}
}
The editor instances are saved to window
as an object, the key is using the element id. Whenever I need the editor instance I only need to call this.editor()
method.
Thank you @EasterPeanut for the solution. Does anybody knows why in @EasterPeanut implementation buttons are not activated when are pressed but only when you press and type your first character (similarly when you deactivate)?
Implementing the onTransaction function solved my issue:
onTransaction: () => {
this.updatedAt = Date.now()
},
@aspyropoulos Thanks!
Maybe docs should be edited no ?
Wow I didn't expect to go down this rabbit hole when I opened the Tiptap setup page. Honestly it looks much easier to just use vanilla JS. It's like a five liner.
Maybe we can build an alpine plugin?
As I said, I have nearly zero experience with Alpine. If someone could send a PR with improvements to the doc page, that would be amazing!
@hanspagel the reactivity system in Alpine v3 is extracted from Vue's reactivity system, so basically a trimmed down version only exposing the reactive
function, will need to take a look at Vue's integration to see if I can be of any help
Did anybody find a solution yet? I’m in the middle of upgrading a huge app to Alpine v3 and this is the only thing that’s holding me back. I've been working on this for days but I just can't figure out how to solve this issue. Maybe @calebporzio can give us a hint? 😇 Using Tiptap with Alpine v2 and Livewire is working flawlessly.
I'm using my own custom EmitterProvider (based loosely on y-websocket.js
) to talk to emitter.io and am not using AlpineJS at all, but also get this error when upgrading from @tiptap/vue3@2.0.0-beta.2
to @tiptap/vue3@2.0.0-beta.91
:
Uncaught RangeError: Applying a mismatched transaction
at EditorState.applyInner (index.es.js:863:33)
at EditorState.applyTransaction (index.es.js:831:1)
at EditorState.apply (index.es.js:807:1)
at Editor.dispatchTransaction (tiptap-core.esm.js:3457:1)
at EditorView.dispatch (index.es.js:5285:29)
at sync-plugin.js:401:1
at ProsemirrorBinding.mux (mutex.js:35:1)
at ProsemirrorBinding._typeChanged (sync-plugin.js:384:1)
at Module.callAll (function.js:19:1)
at callEventHandlerListeners (yjs.mjs:1863:12)
I just wanted to add another data point here while I keep trying to debug it in case others run into the same issue.
Related to this error, I'm also experiencing a "Alpine Expression Error: editor is not defined" caused by a "Uncaught ReferenceError: editor is not defined".
Test project: I followed the instructions in the doc and set up the example with Alpine 3. The basic editor worked BUT as soon as I added BubbleMenu or FloatingMenu it breaks again and Alpine can't find the reference to the "editor" field anymore.
The placeholder extension worked though.
2 workarounds I found:
@azarai Could you please share your (full) solution? :)
As some people described already this is a Proxy issue with Alpine. Alpine is using Proxy to have reactive objects - which is why this error is occuring.
Here you can see that a Proxy is sent to applyTransaction
- since this is not the correct way to apply a transaction prosemirror is throwing this error.
Don't know a solution for now, just so people know more about why and where this is happening.
Just spotted this, and can hopefully point in the right direction, Alpine.js uses the reactive engine from Vue.js 3. Fixing it will be a very similar to what I did to get Vue3 working way back last year... https://github.com/ueberdosis/tiptap/issues/1166
From memory the trick with Vue was to use a ShallowRef() to hold the Editor object, this stops the reactive engine from trying to track all properties and methods within TipTap. (we then did a bunch of stuff to make the toolbars reactive but my memory gives up at that point)
It doesn't look like Alpine exposes the ShallowRef()
api, but that will defiantly be the starting point to getting it working while having the Editor
be in your x-data
.
(as others have mentioned having your editor object outside of x-data
is a good workaround, but obviously not ideal)
I'm not 100% this is a solution but it seems to work in my case? (I need to do more testing).
So you define your editor outside Alpine:
window.editor = null;
Then, within the main x-data function we initialise the editor:
const data= (content: Content = '') => {
async init(element: Element) {
window.editor = new Editor({
// stuff in here obviously
});
}
Then we create a new function so we can access the editor from within our data function:
const data= (content: Content = '') => {
async init(element: Element) {
window.editor = new Editor({
// stuff in here obviously
});
},
editor: () => {
return window.editor;
},
}
Then from your html you can call this:
<button
data-text="Bold"
@click="editor().chain().toggleBold().focus().run()"
:class="{ 'is-active': editor().isActive('bold') }"
>
Bold
</button>
The difference with the above is that instead of calling editor.chain() we call editor().chain().
As far as I can tell almost everything here works except for :class="{ 'is-active': editor().isActive('bold') }"
.
EDIT:
I thought this may have been a better solution, but the error is still there :/
get editor() {
return window.editor;
},
@creativiii yes, exactly what I did https://github.com/ueberdosis/tiptap/issues/1515#issuecomment-937327322 but you might want to put the editor in a collection instead so you access multiple editor instances in case you have multiple editors in a single page.
@creativiii yes, exactly what I did #1515 (comment) but you might want to put the editor in a collection instead so you access multiple editor instances in case you have multiple editors in a single page.
Sorry! I must've completely skipped over your comment 😂
Did you figure out a solution to checking if a button isActive
?
EDIT: On second thought this works just as well?
<button
x-data="{ active: false }"
data-text="Bold"
@click="() => {
editor().chain().toggleBold().focus().run();
active = editor().isActive('bold');
}"
:class="{ 'is-active': active }"
>
Bold
</button>
@creativiii yes, exactly what I did #1515 (comment) but you might want to put the editor in a collection instead so you access multiple editor instances in case you have multiple editors in a single page.
Sorry! I must've completely skipped over your comment 😂 Did you figure out a solution to checking if a button
isActive
?
I have buttons
attribute in my Alpine component. With this attribute, not only I can give a different set of buttons for each editors but also I can use this to hold the buttons state and update the state by listening to onUpdate
and onTransaction
events.
In the html you can just loop the button collection and access the active state like:
<template x-for="button in buttons" :key="button.name" hidden>
<button type="button" :class="{ 'bg-gray-50': button.active }" x-on:click.prevent="clickButton(button.name)">
<span x-text="button.label"></span>
</button>
</template>
Sorry! I must've completely skipped over your comment 😂 Did you figure out a solution to checking if a button
isActive
?I have
buttons
attribute in my Alpine component. With this attribute, not only I can give a different set of buttons for each editors but also I can use this to hold the buttons state and update the state by listening toonUpdate
andonTransaction
events.In the html you can just loop the button collection and access the active state like:
<template x-for="button in buttons" :key="button.name" hidden> <button type="button" :class="{ 'bg-gray-50': button.active }" x-on:click.prevent="clickButton(button.name)"> <span x-text="button.label"></span> </button> </template>
That's really smart! I'll give it a shot.
Not sure if there's a correct way to listen for a value changing with Alpine but this is how my buttons look:
buttons: [
{
active: false,
isActive: () => window.editor?.isActive('bold'),
name: 'bold',
onClick: () => {
window.editor!.chain().toggleBold().focus().run();
},,
},
],
Then in the editor:
onTransaction: ({ transaction }) => {
this.buttons.forEach(button => {
button.active = button.isActive() as boolean;
});
},
This issue is stale because it has been open 45 days with no activity. Remove stale label or comment or this will be closed in 7 days
Since @peterfox added a good note about this to our PHP documentation for Alpine I'd close this issue for now. Hope everyone is happy with that decision. Feel free to reopen if there is still something open to discuss.
You can get the easiest integration of tip with laravel livewire you can view blog: https://medium.com/@dipeshmurmu/effortless-integration-harnessing-tiptap-editor-with-laravel-livewire-b0fc9fe4d5f1
Description I got
Range Error: Applying a mismatched transaction
when clicking on a menu button with AlpineJS v3.CodeSandbox I created a CodeSandbox to help you debug the issue: https://codesandbox.io/s/tiptap-v2-alpinejs-v3-s5x2o