iliyaZelenko / tiptap-vuetify

Vuetify editor. Component simplifies integration tiptap editor with vuetify.
https://iliyazelenko.github.io/tiptap-vuetify-demo/
800 stars 127 forks source link

Tiptap doesn't load extensions on first load (page refresh) #339

Closed woottonn closed 2 years ago

woottonn commented 2 years ago

Hello!

I'm loving Tiptap, although I am encountering a rather confusing error.

app.js?v=1652263888:218427 [Vue warn]: Error in mounted hook: "TypeError: Cannot read properties of undefined (reading 'framework')"

found in

---> <N>
       <Agenda> at resources/js/meetings/Agenda.vue
         <VForm>
           <VCard>... (1 recursive calls)
             <Meeting> at resources/js/meetings/Meeting.vue
               <Mainapp> at resources/js/components/mainapp.vue
                 <VMain>
                   <VApp>
                     <Root>
warn @ app.js?v=1652263888:218427
logError @ app.js?v=1652263888:219690
globalHandleError @ app.js?v=1652263888:219685
handleError @ app.js?v=1652263888:219645
invokeWithErrorHandling @ app.js?v=1652263888:219668
callHook @ app.js?v=1652263888:222020
insert @ app.js?v=1652263888:220943
invokeInsertHook @ app.js?v=1652263888:224145
patch @ app.js?v=1652263888:224364
Vue._update @ app.js?v=1652263888:221746
updateComponent @ app.js?v=1652263888:221867
get @ app.js?v=1652263888:222280
Watcher @ app.js?v=1652263888:222269
mountComponent @ app.js?v=1652263888:221874
Vue.$mount @ app.js?v=1652263888:226851
Vue.$mount @ app.js?v=1652263888:229760
Vue._init @ app.js?v=1652263888:222815
Vue @ app.js?v=1652263888:222882
./resources/js/app.js @ app.js?v=1652263888:54491
__webpack_require__ @ app.js?v=1652263888:290405
(anonymous) @ app.js?v=1652263888:290575
__webpack_require__.O @ app.js?v=1652263888:290442
(anonymous) @ app.js?v=1652263888:290577
(anonymous) @ app.js?v=1652263888:290579
app.js?v=1652263888:219694 TypeError: Cannot read properties of undefined (reading 'framework')
    at t.get [as vuetifyLang] (app.js?v=1652263888:120802:355999)
    at Sr (app.js?v=1652263888:120802:53825)
    at new t (app.js?v=1652263888:120802:84205)
    at Object.get [as availableActions] (app.js?v=1652263888:120802:87049)
    at app.js?v=1652263888:120802:80389
    at Array.forEach (<anonymous>)
    at VueComponent.n.mounted (app.js?v=1652263888:120802:80292)
    at invokeWithErrorHandling (app.js?v=1652263888:219660:57)
    at callHook (app.js?v=1652263888:222020:7)
    at Object.insert (app.js?v=1652263888:220943:7)

If I try to load in extensions, I get this error on first page load, however, if I routerview out and back in, it works fine.

This only happens on one particular page, other pages that use tiptap don't seem to have this issue.

This is the particular page:

<template>
    <div v-if="checkPermission('meetings')">

        <v-card tile elevation="0">
            <v-toolbar
                color="primary"
                dark
                tile
                dense
                elevation="0"
                class="ml-0 pl-0"
            >
                <v-icon class="mr-2">mdi-flag-triangle</v-icon>
                <v-toolbar-title class="text" @click="descriptionDialog=true">
                    <v-tooltip bottom v-if="checkPermissionMeetings('write', areaID)">
                        <template v-slot:activator="{ on, attrs }">
                            <span
                                v-bind="attrs"
                                v-on="on"
                                style="cursor:pointer"
                            >
                                {{action.description}}
                            </span>
                        </template>
                        <span>Click to edit this action name</span>
                    </v-tooltip>
                    <span v-else>{{action.description}}</span>
                </v-toolbar-title>
                <v-dialog
                    v-model="descriptionDialog"
                    width="400px"
                    :transition="transitionSiteWide()"
                >
                    <v-card elevation="0">
                        <v-toolbar
                            dark
                            class="primary"
                            dense
                            elevation="0"
                        >
                            <v-icon class="mr-2">mdi-pencil</v-icon>
                            <v-toolbar-title class="text">Edit Action Name</v-toolbar-title>
                            <v-spacer></v-spacer>
                        </v-toolbar>
                        <v-card-text class="pb-3 pt-4">
                            <v-text-field
                                v-model="action.description"
                                label="Description"
                                outlined
                                :rules="rules.description"
                                dense
                                v-on:keyup.enter="updateDescription()"
                            ></v-text-field>
                        </v-card-text>
                        <v-card-actions>
                            <v-btn
                                text
                                @click="descriptionDialog = false"
                            >
                                Close
                            </v-btn>
                            <v-spacer></v-spacer>
                            <v-btn
                                class="primary"
                                text
                                :disabled="!action.description||process.description"
                                :loading="process.description"
                                @click="updateDescription();"
                            >
                                Save
                            </v-btn>
                        </v-card-actions>
                    </v-card>
                </v-dialog>
                <v-spacer></v-spacer>

                <div class="text-right">
                    <v-tooltip bottom v-if="checkPermissionMeetings('write', areaID)&&$vuetify.breakpoint.mdAndUp">
                        <template v-slot:activator="{ on, attrs }">
                            <v-icon
                                color="red"
                                @click="deleteDialog=true;"
                                v-bind="attrs"
                                v-on="on"
                            >
                                mdi-delete
                            </v-icon>
                        </template>
                        <span>Delete</span>
                    </v-tooltip>

                    <v-dialog
                        v-model="deleteDialog"
                        width="400px"
                        :transition="transitionSiteWide()"
                    >
                        <v-card elevation="0">
                            <v-toolbar
                                dark
                                class="primary"
                                dense
                                elevation="0"
                            >
                                <v-icon class="mr-2">mdi-sign-direction-minus</v-icon>
                                <v-toolbar-title class="text">{{action.description}}</v-toolbar-title>
                                <v-spacer></v-spacer>
                            </v-toolbar>
                            <v-card-text class="pb-0">
                                <v-container>
                                    <p>Are you sure you want to delete this action point?</p>
                                    <p>{{action.description}}
                                        will be removed from the system.</p>
                                </v-container>
                            </v-card-text>
                            <v-card-actions>
                                <v-btn
                                    text
                                    @click="deleteDialog = false"
                                >
                                    Close
                                </v-btn>
                                <v-spacer></v-spacer>
                                <v-btn
                                    class="primary"
                                    text
                                    :disabled="process.delete"
                                    :loading="process.delete"
                                    @click="deleteAction()"
                                >
                                    Yes, delete this action point
                                </v-btn>
                            </v-card-actions>
                        </v-card>
                    </v-dialog>

                    <v-btn
                        color="grey"
                        class="ma-2 mr-0 white--text d-inline-block"
                        small
                        @click="[action.expand = !action.expand,resize()]"
                    >
                        <v-icon
                            left
                            color="white"
                            v-show="!action.expand"
                        >
                            mdi-arrow-expand-vertical
                        </v-icon>
                        <v-icon
                            left
                            color="white"
                            v-show="action.expand"
                        >
                            mdi-arrow-collapse-vertical
                        </v-icon>
                        <span v-show="!action.expand">Expand</span>
                        <span v-show="action.expand">Collapse</span>
                    </v-btn>
                </div>

            </v-toolbar>
            <v-expand-transition>
                <v-card-text class="pa-0" v-show="action.expand">
                    <v-row no-gutters>
                        <v-col cols="12" class="pa-0">
                            <v-toolbar
                                color="grey lighten-3"
                                class="pl-1"
                                light
                                dense
                                elevation="0"
                            >
                                <span>Completion</span>
                                <v-spacer></v-spacer>
                                <div style="width:150px;" v-if="checkPermissionMeetings('write', areaID)">
                                    <v-select
                                        v-model="action.completion"
                                        dense
                                        dark
                                        :items="completion"
                                        outlined
                                        item-value="value"
                                        item-text="text"
                                        hide-details
                                        style="color:#fff"
                                        :style="parseInt(action.completion)===1 ? 'background-color:#2a7724;' : 'background-color:#e16f00;'"
                                        :menu-props="{ bottom: true, offsetY: true }"
                                    ></v-select>
                                </div>
                                <v-chip
                                    v-else
                                    :style="parseInt(action.completion)===1 ? 'background-color:#2a7724;' : 'background-color:#e16f00;'"
                                    class="text-white"
                                >
                                    <span v-if="parseInt(action.completion)===1">Complete</span>
                                    <span v-else>In Progress</span>
                                </v-chip>
                            </v-toolbar>
                        </v-col>
                    </v-row>
                    <v-row class="mt-0 pb-2">
                        <v-col cols="12" md="4" lg="3" class="pa-6 pl-8 text-left">
                            <v-tooltip bottom>
                                <template v-slot:activator="{ on, attrs }">
                                    <span
                                        v-bind="attrs"
                                        v-on="on"
                                    >Started {{action.pretty_date_from_for_humans}}</span>
                                </template>
                                <span>{{action.pretty_date_from}}</span>
                            </v-tooltip>
                        </v-col>
                        <v-col cols="12" md="4" lg="6" class="pa-6">
                            <v-tooltip bottom>
                                <template v-slot:activator="{ on, attrs }">
                                    <v-progress-linear
                                        :value="action.due_percentage"
                                        rounded
                                        :color="action.due_color"
                                        height="14"
                                        width="100%"
                                        class="mt-1"
                                        v-bind="attrs"
                                        v-on="on"
                                    ></v-progress-linear>
                                </template>
                                <span>{{action.due_text}}</span>
                            </v-tooltip>
                        </v-col>
                        <v-col cols="12" md="4" lg="3" class="pa-6 pr-8 text-right">
                            <v-tooltip bottom>
                                <template v-slot:activator="{ on, attrs }">
                                        <span
                                            v-bind="attrs"
                                            v-on="on"
                                            @click="checkPermissionMeetings('write', areaID) ? toDialog=true : ''"
                                            :style="checkPermissionMeetings('write', areaID) ? 'cursor:pointer;' : ''"
                                        >Due {{action.pretty_date_to_for_humans}}</span>
                                </template>
                                <span>{{action.pretty_date_to}}</span>
                            </v-tooltip>
                            <v-dialog
                                v-model="toDialog"
                                ref="dialog"
                                width="290px"
                                :transition="transitionSiteWide()"
                            >
                                <v-date-picker
                                    v-model="action.date_to"
                                    scrollable
                                >
                                    <v-btn
                                        @click="toDialog = false"
                                        elevation="0"
                                    >
                                        Close
                                    </v-btn>
                                    <v-spacer></v-spacer>
                                </v-date-picker>
                            </v-dialog>
                        </v-col>
                    </v-row>
                    <v-row no-gutters>
                        <v-col cols="12" lg="5" class="pa-0">
                            <v-toolbar
                                color="grey lighten-3"
                                class="pl-1"
                                light
                                dense
                                elevation="0"
                            >
                                Progress &amp; Completion Notes
                                <v-spacer></v-spacer>
                            </v-toolbar>
                            <div class="pa-3 text-left" v-show="checkPermissionMeetings('write', areaID)">
                                <TiptapVuetify
                                    v-model="action.notes"
                                    :extensions="tipTapExtensions"
                                    :toolbar-attributes="tipTapAttributes"
                                    :card-props="tipTapCardProps"
                                />
                            </div>
                            <div v-show="!checkPermissionMeetings('write', areaID)" class="text-left mt-3 pa-3 ml-3 custom-rounded-border pa-4" style="height:260px;overflow-y:auto" v-html="action.notes"></div>
                        </v-col>
                        <v-col cols="12" lg="7" class="pa-0">
                            <v-toolbar
                                color="grey lighten-3"
                                class="pl-0"
                                light
                                dense
                                elevation="0"
                            >
                                Files
                                <v-spacer></v-spacer>
                                <v-btn
                                    color="teal"
                                    class="ma-2 white--text d-inline-block"
                                    small
                                    @click="fileUploadDialog = true;fileUploadGDoc = true;"
                                    v-if="checkPermissionMeetings('write', areaID)"
                                >
                                    <v-icon
                                        left
                                        dark
                                        :class="$vuetify.breakpoint.mdAndUp ? 'mr-2' : 'mr-0'"
                                    >
                                        mdi-google-drive
                                    </v-icon>
                                    <span v-show="$vuetify.breakpoint.smAndUp">Link a Google Doc</span>
                                </v-btn>
                                <v-btn
                                    color="primary"
                                    class="ma-2 white--text d-inline-block"
                                    small
                                    @click="fileUploadDialog = true"
                                    v-if="checkPermissionMeetings('write', areaID)"
                                >
                                    <v-icon
                                        left
                                        dark
                                        :class="$vuetify.breakpoint.mdAndUp ? 'mr-2' : 'mr-0'"
                                    >
                                        mdi-cloud-upload
                                    </v-icon>
                                    <span v-show="$vuetify.breakpoint.smAndUp">Upload File(s)</span>
                                </v-btn>
                            </v-toolbar>
                            <FileList ref="files" :passFiles="true" :passedFiles="action.files" class="ma-3 pa-0" :forceHeight="260" style="overflow-y:auto" :id="action.id" type="meeting_action" :showActions="checkPermissionMeetings('write', areaID)" :paginate="3" :fileUploadDialog="fileUploadDialog" :fileUploadGDoc="fileUploadGDoc" :ok="ok"></FileList>
                        </v-col>
                    </v-row>
                    <v-row no-gutters>
                        <v-col cols="12" class="pa-0">
                            <v-toolbar
                                color="grey lighten-3"
                                class="pl-1"
                                light
                                dense
                                elevation="0"
                            >
                                <span>Responsibility</span>
                                <v-spacer></v-spacer>
                                <div>
                                    <div
                                        @click="checkPermissionMeetings('write', areaID) ? users=true : ''"
                                        :style="checkPermissionMeetings('write', areaID) ? 'cursor:pointer' : ''"
                                    >
                                        <div v-show="!action.users.length" style="opacity:0.6">
                                            <v-tooltip bottom v-show="action.users.length">
                                                <template v-slot:activator="{ on, attrs }">
                                                    <div
                                                        v-bind="attrs"
                                                        v-on="on"
                                                    >
                                                        Whole Responsibility
                                                    </div>
                                                </template>
                                                <span v-if="checkPermissionMeetings('write', areaID)">Click to assign responsibility to users</span>
                                                <span v-else>There is no responsibility set for this objective</span>
                                            </v-tooltip>

                                        </div>
                                        <v-tooltip bottom v-for="user in action.users" :key="user.id" v-show="action.users.length">
                                            <template v-slot:activator="{ on, attrs }">
                                                <v-avatar
                                                    size="26px"
                                                    class="ml-2"
                                                    v-bind="attrs"
                                                    v-on="on"
                                                >
                                                    <v-img
                                                        :src="'/img/profile-pictures/'+user.profile_photo_thumb"
                                                    />
                                                </v-avatar>
                                            </template>
                                            <span>{{user.full_name}}</span>
                                        </v-tooltip>
                                    </div>
                                    <v-dialog
                                        v-model="users"
                                        width="400px"
                                        persistent
                                        :transition="transitionSiteWide()"
                                    >
                                        <v-card elevation="0">
                                            <v-toolbar
                                                dark
                                                class="primary"
                                                dense
                                                elevation="0"
                                            >
                                                <v-icon class="mr-2">mdi-account-multiple</v-icon>
                                                <v-toolbar-title class="text">Responsibility</v-toolbar-title>
                                                <v-spacer></v-spacer>
                                            </v-toolbar>
                                            <v-card-text class="pb-3 pt-4">
                                                <v-select
                                                    :items="meeting.write_users"
                                                    label="Responsibility (Leave empty for whole responsibility)"
                                                    item-value="id"
                                                    item-text="full_name"
                                                    multiple
                                                    dense
                                                    hide-details
                                                    outlined
                                                    v-model="action.users_only_id"
                                                    :menu-props="{ bottom: true, offsetY: true }"
                                                >
                                                    <template v-slot:item="{item}">
                                                        <v-avatar
                                                            size="30px"
                                                            class="mr-3"
                                                        >
                                                            <v-img :src="'/img/profile-pictures/'+item.profile_photo_thumb" />                                                        </v-avatar>
                                                        {{ item.full_name }}
                                                    </template>
                                                </v-select>
                                            </v-card-text>
                                            <v-card-actions>
                                                <v-btn
                                                    text
                                                    @click="users = false"
                                                >
                                                    Close
                                                </v-btn>
                                                <v-spacer></v-spacer>
                                                <v-btn
                                                    class="primary"
                                                    text
                                                    :disabled="process.users"
                                                    :loading="process.users"
                                                    @click="updateUsers();"
                                                >
                                                    Save
                                                </v-btn>
                                            </v-card-actions>
                                        </v-card>
                                    </v-dialog>
                                </div>
                            </v-toolbar>
                        </v-col>
                    </v-row>
                </v-card-text>
            </v-expand-transition>
        </v-card>

    </div>
</template>

<script>
    import {mapGetters} from "vuex";
    import FileList from "../components/FileList";

    export default {
        components: {
            FileList
        },
        props: {
            actionID: Number,
            agendaID: Number,
            incomplete: Boolean,
        },
        data () {
            return {
                dataLoading: true,
                fileUploadDialog: false,
                fileUploadGDoc: false,
                initialLoad: true,
                editorHeight: 0,
                completion: [
                    {text: 'In Progress', value: 0},
                    {text: 'Complete', value: 1},
                ],
                fromDialog: false,
                toDialog: false,
                deleteDialog: false,
                users: false,
                descriptionDialog: false,
                rules: {
                    description : [
                        v => !!v || 'A description is required',
                    ],
                },
                process: {
                    description: false,
                    users: false,
                    delete: false,
                },
                awaiting: null,
            }
        },
        computed:{
            ...mapGetters({
                meeting: 'meetings/getEditingMeeting',
                getAction: 'meetings/getAction',
                getActionIncomplete: 'meetings/getActionIncomplete',
                working: 'meetings/getWorkingProcess',
            }),
            areaID(){
                return this.$route.params.areaID;
            },
            meetingID(){
                return this.$route.params.meetingID;
            },
            action(){
                if(this.incomplete) { //If fetching incomplete action
                    return this.getActionIncomplete(this.agendaID, this.actionID);
                }else{
                    return this.getAction(this.agendaID, this.actionID);
                }
            },
        },
        watch: {
            'action.notes': {
                handler: function() {
                    if (!this.awaiting) {
                        setTimeout(() => {
                            this.updateNotes();
                            this.awaiting = false;
                        }, 2000);
                    }
                    this.awaiting = true;
                },
                immediate: false,
            },
            'action.completion': {
                handler: function() {
                    this.updateCompletion();
                },
                immediate: false,
            },
            'action.date_from': {
                handler: function() {
                    this.updateDates();
                },
                immediate: false,
            },
            'action.date_to': {
                handler: function() {
                    this.updateDates();
                },
                immediate: false,
            },
        },
        methods:{
            ok(){
                this.fileUploadDialog = false;
                this.fileUploadGDoc = false;
            },
            async updateNotes(){
                this.workingProcess(true);
                const res = await this.callApi('post', '/app/meetings/updateActionNotes', this.action);
                if(res.status===201){

                }else{
                    if(res.status===422){
                        for(let i in res.data.errors){
                            this.snackbar(res.data.errors[i][0], 'error');
                        }
                    }else{
                        this.snackbar('There has been a problem, we don\'t have any more information for you', 'error');
                    }

                }
                this.workingProcess(false);
            },
            async updateDescription(){
                this.workingProcess(true);
                this.process.description = true;
                this.action.meetingID = this.meetingID;
                this.action.agendaID = this.agendaID;
                const res = await this.callApi('post', '/app/meetings/updateActionDescription', this.action);
                if(res.status===201){

                }else{
                    if(res.status===422){
                        for(let i in res.data.errors){
                            this.snackbar(res.data.errors[i][0], 'error');
                        }
                    }else{
                        this.snackbar('There has been a problem, we don\'t have any more information for you', 'error');
                    }

                }
                this.workingProcess(false);
                this.process.description = true;
                this.descriptionDialog = false;
            },
            async updateCompletion(){
                this.workingProcess(true);
                this.action.meetingID = this.meetingID;
                this.action.meeting_date = this.meeting.date_on;
                this.action.agendaID = this.agendaID;
                const res = await this.callApi('post', '/app/meetings/updateActionCompletion', this.action);
                if(res.status===201){
                    //this.getActionCompletion();
                    res.data.expand = true;
                    if(this.incomplete) { //If fetching incomplete action
                        this.$store.commit('meetings/updateActionIncomplete', res.data);
                    }else{
                        this.$store.commit('meetings/updateAction', res.data);
                    }
                }else{
                    if(res.status===422){
                        for(let i in res.data.errors){
                            this.snackbar(res.data.errors[i][0], 'error');
                        }
                    }else{
                        this.snackbar('There has been a problem, we don\'t have any more information for you', 'error');
                    }

                }
                this.workingProcess(false);
            },
            async updateDates(){
                this.workingProcess(true);
                const res = await this.callApi('post', '/app/meetings/updateActionDates', this.action);
                if(res.status===201){
                    //this.getactionCompletion();
                    res.data.expand = true;
                    if(this.incomplete) { //If fetching incomplete action
                        this.$store.commit('meetings/updateActionIncomplete', res.data);
                    }else{
                        this.$store.commit('meetings/updateAction', res.data);
                    }
                }else{
                    if(res.status===422){
                        for(let i in res.data.errors){
                            this.snackbar(res.data.errors[i][0], 'error');
                        }
                    }else{
                        this.snackbar('There has been a problem, we don\'t have any more information for you', 'error');
                    }
                }
                this.$store.commit('improvement/updateWorkingProcess',false);
                this.process.dates = false;
                this.fromDialog = false;
                this.toDialog = false;
                this.workingProcess(false);
            },
            async updateUsers(){
                this.process.users = true;
                this.workingProcess(true);
                const res = await this.callApi('post', '/app/meetings/updateActionUsers', this.action);
                if(res.status===201){
                    res.data.expand = true;
                    if(this.incomplete) { //If fetching incomplete action
                        this.$store.commit('meetings/updateActionIncomplete', res.data);
                    }else{
                        this.$store.commit('meetings/updateAction', res.data);
                    }
                }else{
                    if(res.status===422){
                        for(let i in res.data.errors){
                            this.snackbar(res.data.errors[i][0], 'error');
                        }
                    }else{
                        this.snackbar('There has been a problem, we don\'t have any more information for you', 'error');
                    }

                }
                this.workingProcess(true);
                this.process.users = false;
                this.users = false;
            },
            async deleteAction(){
                this.process.delete = true;
                this.workingProcess(true);
                const res = await this.callApi('post', '/app/meetings/deleteAction', this.action);
                if(res.status===201){
                    if(this.incomplete) { //If fetching incomplete action
                        this.$store.commit('meetings/deleteActionIncomplete', this.action);
                    }else{
                        this.$store.commit('meetings/deleteAction', this.action);
                    }
                }else{
                    if(res.status===422){
                        for(let i in res.data.errors){
                            this.snackbar(res.data.errors[i][0], 'error');
                        }
                    }else{
                        this.snackbar('There has been a problem, we don\'t have any more information for you', 'error');
                    }

                }
                this.workingProcess(true);
                this.process.delete = false;
                this.deleteDialog = false;
            },

            workingProcess(state){
                this.$store.commit('meetings/updateWorkingProcess',state);
            },
            onResize() {
                //this.editorHeight = this.$refs.files.$el.clientHeight;
            },
            resize(){
                setTimeout(() =>  window.dispatchEvent(new Event('resize')), 1);
            },

        },
        async mounted(){
            this.onResize();
        },
        created(){
            window.addEventListener("resize", this.onResize);
        },
        destroyed(){
            window.removeEventListener("resize", this.onResize);
        },

    }
</script>

<style scoped>
    ::v-deep .tiptap-vuetify-editor__content { height: 217px !important; }
</style>

Is there something silly I'm missing here?

woottonn commented 2 years ago

Ok, turns out...

import { TiptapVuetifyPlugin } from 'tiptap-vuetify'
//The following section of code, originallu I had after mew "new Vue({}) - it needed to be placed before this!
Vue.use(TiptapVuetifyPlugin, {
    // the next line is important! You need to provide the Vuetify Object to this place.
    vuetify, // same as "vuetify: vuetify"
    // optional, default to 'md' (default vuetify icons before v2.0.0)
    iconsGroup: 'mdi'
});