Search code examples
javascriptvue.jsquill

How to fix error initializing offset in vue quill


I have problem initializing vue quill to apply in a Vue3 project about a note taking function, i can make note easily but when modifying it, it return offset error everytime i click or type on the quill editor. Here is my error

quill.js?v=870ff25b:12570 Uncaught TypeError: Cannot read properties of null (reading 'offset')
    at quill.js?v=870ff25b:12570:26
    at Array.map (<anonymous>)
    at Selection.normalizedToRange (quill.js?v=870ff25b:12567:31)
    at Selection.getRange (quill.js?v=870ff25b:12556:25)
    at Selection.update (quill.js?v=870ff25b:12691:43)
    at quill.js?v=870ff25b:12452:12
    at quill.js?v=870ff25b:12338:9
    at Array.forEach (<anonymous>)
    at Emitter.handleDOM (quill.js?v=870ff25b:12332:43)
    at quill.js?v=870ff25b:12310:23

And here is my code:

<template>
  <section id="view-note-page" class="container mt-4">
    <div class="note-header-container mb-3 d-flex justify-content-between align-items-center">
      <div class="note-title-container">
        <label for="note-title" class="note-title-label">Title:</label>
        <input
            id="note-title"
            type="text"
            v-model="noteData.title"
            class="note-title-input"
            placeholder="Enter note title"
        />
      </div>
      <button class="btn btn-primary btn-save-changes" @click="saveNote">Save Changes</button>
    </div>

    <div id="post-editor" class="mb-3 flex-grow-1">
      <div id="quill-editor"></div>
    </div>
  </section>
</template>

<script>
import { nextTick } from "vue";
import Quill from "quill";
import "quill/dist/quill.snow.css";
import { toast } from "vue3-toastify";
import "vue3-toastify/dist/index.css";
import { noteService } from "@/services/note.service";

export default {
  name: "ViewNotePage",
  props: {
    noteId: {
      type: [String, Number],
      required: true,
    },
  },
  data() {
    return {
      noteData: {
        title: "",
        content: "",
      },
      quillEditor: null,
    };
  },
  methods: {
    async fetchNote() {
      try {
        const note = await noteService.fetchNoteFromId(this.noteId);
        this.noteData = note;
        await this.updateQuillContent(note.content);
      } catch (error) {
        console.error("Failed to fetch the note:", error);
        toast.error("Failed to fetch the note.", { autoClose: 2000 });
      }
    },
    async initializeQuill() {
      try {
        await nextTick();
        const editorElement = document.getElementById("quill-editor");

        if (editorElement) {
          this.quillEditor = new Quill(editorElement, {
            theme: "snow",
            modules: {
              toolbar: [
                ["bold", "italic", "underline"],
                [{list: "ordered"}, {list: "bullet"}],
                [{header: [1, 2, 3, false]}],
                ["link"],
                ["clean"],
              ],
            },
          });

          this.quillEditor.on("text-change", () => {
            this.noteData.content = JSON.stringify(this.quillEditor.getContents());
          });
        }
      } catch (error) {
        console.error("Failed to initialize Quill editor:", error);
      }
    },
    async updateQuillContent(content) {
      try {
        if (this.quillEditor) {
          const delta = content ? JSON.parse(content) : {ops: []};
          this.quillEditor.setContents(delta);
        }
      } catch (error) {
        console.error("Failed to update Quill content:", error);
      }
    },
    async saveNote() {
      try {
        if (!this.noteData.title || !this.noteData.content) {
          toast.error("Please provide a title and content for the note.", {autoClose: 2000});
          return;
        }

        await noteService.updateNote(this.noteId, this.noteData);
        toast.success("Note updated successfully!", {autoClose: 2000});
        this.$emit("update", this.noteData);
      } catch (error) {
        console.error("Failed to save the note:", error);
        toast.error("Failed to update the note.", {autoClose: 2000});
      }
    },
  },
  async mounted() {
    try {
      await this.initializeQuill();
      await this.fetchNote();
    } catch (error) {
      console.error("Error during component setup:", error);
    }
  },
  watch: {
    noteId: {
      immediate: true,
      handler(newNoteId) {
        this.fetchNote();
      },
    },
  },
};
</script>

I tried to use something similar to this I found on github but it doesnot work

// ✅  works with quill2
let quill: Quill | null = null

onMounted(() => {
   quill = new Quill(...)
})

Solution

  • As explained in this issue, the problem is that Quill object is incompatible with Vue reactive proxy.

    This can be avoided by marking it as non-reactive:

    this.quillEditor = markRaw(new Quill(...));
    

    The solution that is specific to options API is to not make quillEditor reactive data and remove it from data.