Vue 2 Nuxt directive image source won't work - image

Can someone tell me why this does not work in Nuxt, or how to fix it. The dataset.alt attribute is simply 'image.svg'
el.setAttribute('src', '~/assets/images/' + el.dataset.alt)

The answer was to use directive binding and to require the image. Full code for reference:
<img src="#/assets/images/light-image.svg" v-theme-image="require('#/assets/images/dark-image.svg')" />
import Vue from 'vue';
import VueCookies from 'vue-cookies'
Vue.use(VueCookies)
export const themeImage = {
inserted: (el, binding, vnode) => {
function init() {
if ($cookies.get('theme') === 'dark') {
el.setAttribute('src', binding.value);
}
}
init();
},
}
Vue.directive('theme-image', themeImage);

Related

Default Persistent Layout In Laravel + Inertia + Vite

In the previous way of setting up inertia in a laravel app, I could tweak the resolve property in the `createInertiaApp function from:
{
...,
resolve: name => import("./Pages/${name}"),
...
}
To
{
...,
resolve: name => {
const page = require("./Pages/${name}").default
if(!page.layout) {
page.layout = DefaultLayoutFile
}
},
...
}
To allow me manually pass a default layout file to be used in pages.
But with Vite becoming the default asset bundler and according to the docs, I must use a resolvePageComponent function which takes in import.meta.glob as a second argument to instruct Vite which files to bundle.
Problem here is the import gets returned from this resolvePageComponent so I cannot access the default object like I normally will from a require function.
So I have not been able to attach a default layout file to imported pages.
Has anyone been able to find a workaround for this?
Assuming you imported your default layout file like this (remember to not use # in imports anymore as only relative paths work for static analysis reasons):
import DefaultLayoutFile from './Layouts/DefaultLayoutFile.vue'
You can use the following code to get default layout working with Inertia and Vite:
resolve: (name) => {
const page = resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob("./Pages/**/*.vue")
);
page.then((module) => {
module.default.layout = module.default.layout || DefaultLayoutFile;
});
return page;
},
[UPDATE 2022-08-01]
Since this is still receiving views, I though it would be useful to show how to get the # working in imports in Vite.
Require path below your imports in vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '#vitejs/plugin-vue';
const path = require('path')
And then add resolve into your config below:
export default defineConfig({
resolve:{
alias:{
'#' : path.resolve(__dirname, './src')
},
},
})
Now # will point to your Laravel root and you can import components dynamically from anywhere:
For example import LayoutTop from '#/Layouts/LayoutTop.vue'
will now point to
/resources/js/Layouts/LayoutTop.vue
Remember that Vite needs the .vue extension when importing Vue SFC files.
The async await version of the accepted answer
resolve: async name => {
const page = await resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob("./Pages/**/*.vue"));
page.default.layout ??= DefaultLayoutFile;
return page;
},
This worked from me using the vite with inertia preset
import { createApp, h } from 'vue'
import { createInertiaApp } from '#inertiajs/inertia-vue3'
import { resolvePageComponent } from 'vite-plugin-laravel/inertia'
import DefaultLayout from '#/views/layouts/default.vue'
import SimpleLayout from '#/views/layouts/simple.vue'
import UserLayout from '#/views/layouts/user.vue'
createInertiaApp({
resolve: (name) => {
const page = resolvePageComponent(
name,
import.meta.glob('../views/pages/**/*.vue'),
DefaultLayout
);
page.then((module) => {
if (name.startsWith('auth.')) module.layout = SimpleLayout;
if (name.startsWith('user.')) module.layout = [DefaultLayout, UserLayout];
});
return page
},
setup({ el, app, props, plugin }) {
createApp({ render: () => h(app, props) })
.use(plugin)
.mount(el)
},
})

Svelte Library [Ckeditor 5] works as node module but not locally

I'm trying to add Ckeditor5 to Sveltekit.
Using the node module works perfectly. I import the library onMount and use it.
// Works flawlessly
<script>
import { onMount } from 'svelte';
let Editor;
onMount(async () => {
const module = await import('#ckeditor/ckeditor5-build-balloon-block');
Editor = module.default;
Editor.create(document.querySelector('#editor'), {}).then((editor) => {
console.log(editor);
});
});
</script>
If I try to import a local build, however, module.default is always undefined. The same happens even when I just copy the node_module.
<script>
import { onMount } from 'svelte';
let Editor;
onMount(async () => {
// Import changed to local build
const module = await import('src/lib/ckeditor');
Editor = module.default;
Editor.create(document.querySelector('#editor'), {}).then((editor) => {
console.log(editor);
});
});
</script>
It's also worth noting that logging the local module just prints:
Module {Symbol(Symbol.toStringTag): 'Module'} to the console.
Can you share your /src/lib/ckeditor file contents?
Sidenote. when accessing DOM elements, you can do the folowing instead of using document.querySelector
<script>
let editorEl;
onMount(()=>{
Editor.create(editorEl, {})
})
</script>
<div bind:this={editorEl}></div>

this.props.editor.create is not a function in CKEditor - NextJS

I'm following the steps from this. And my CKEditor now can run on my nextjs app. But the problem is when I wanna put simpleUploadAdapter, there is an error message saying props.editor.create is not a function. Here's the code :
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import React, { useState, useEffect, useRef } from 'react'
export default function Home() {
const editorCKRef = useRef()
const [editorLoaded, setEditorLoaded] = useState(false)
const { CKEditor, SimpleUploadAdapter, ClassicEditor } = editorCKRef.current || {}
useEffect(() => {
editorCKRef.current = {
CKEditor: require('#ckeditor/ckeditor5-react'),
// SimpleUploadAdapter: require('#ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter'),
ClassicEditor: require('#ckeditor/ckeditor5-build-classic')
}
setEditorLoaded(true)
}, [])
return (
<div className={styles.container}>
<Head>
<title>My CKEditor 5</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<h2>Using CKEditor 5 build in Next JS</h2>
{editorLoaded && ClassicEditor &&
<CKEditor
name="editor"
editor={ typeof ClassicEditor !== 'undefined' ?
ClassicEditor.create(
document.getElementsByName("editor"), {
plugins: [ SimpleUploadAdapter],
//toolbar: [ ... ],
simpleUpload: {
// The URL that the images are uploaded to.
uploadUrl: 'http://example.com',
// Enable the XMLHttpRequest.withCredentials property.
withCredentials: false
}
}
): ''
}
data="<p>Hello from CKEditor 5!</p>"
onInit={ editor => {
// You can store the "editor" and use when it is needed.
console.log( 'Editor is ready to use!', editor );
} }
onChange={ ( event, editor ) => {
const data = editor.getData();
console.log('ON CHANGE')
// console.log(ClassicEditor.create())
// console.log( { event, editor, data } );
} }
onBlur={ ( event, editor ) => {
console.log( 'Blur.', editor );
} }
onFocus={ ( event, editor ) => {
console.log( 'Focus.', editor );
} }
config={
{
simpleUpload: {
uploadUrl: 'localhost:8000/api/files/upload/question/1'
}
}
}
/>
}
</div>
)
}
and this is the error:
So what's the problem in here? Thank you
I got mine to work by wrapping the CKEditor component in a class component of my own.
class RichTextEditor extends React.Component<Props, State> {
render() {
const { content } = this.props;
return (
<CKEditor
editor={ClassicEditor}
data={content}
/>
);
}
}
It seems CKEditor just doesn't play nice with function components. Then use dynamic import to load the wrapper if you're using NextJS.
const RichTextEditor = dynamic(() => import("/path/to/RichTextEditor"), {
ssr: false,
});
I remembered that CKEditor4 is easier to setup in Next.js.
CKEditor5 require more work, you have to use dynamic import with mode ssr=false
But in your case, you also want to use another plugin SimpleUploadAdapter
I tried using CKEditor React component + build classic + SimpleUploadAdapter but meets the error "Code duplication between build classic and source (SimpleUploadAdapter)".
So I decided to custom the ckeditor5-build-classic, add the plugin into there and rebuild, then make it works :)(https://ckeditor.com/docs/ckeditor5/latest/builds/guides/development/custom-builds.html)
Here are few remarks:
Custom ckeditor5-build-classic
// ckeditor5-build-classic-custom local package
// Add SimpleUploadAdapter into the plugin list
// src/ckeditor.js
import SimpleUploadAdapter from '#ckeditor/ckeditor5-upload/src/adapters/simpleuploadadapter';
ClassicEditor.builtinPlugins = [
...
SimpleUploadAdapter
...
]
// Rebuild for using in our app
npm run build
Use the custom build in our app
// app/components/Editor.js
import CKEditor from "#ckeditor/ckeditor5-react";
import ClassicEditor from "#ckeditor/ckeditor5-build-classic";
...
<CKEditor
editor={ClassicEditor}
config={{
// Pass the config for SimpleUploadAdapter
// https://ckeditor.com/docs/ckeditor5/latest/features/image-upload/simple-upload-adapter.html
simpleUpload: {
// The URL that the images are uploaded to.
uploadUrl: "http://example.com",
// Enable the XMLHttpRequest.withCredentials property.
withCredentials: true,
// Headers sent along with the XMLHttpRequest to the upload server.
headers: {
"X-CSRF-TOKEN": "CSRF-Token",
Authorization: "Bearer <JSON Web Token>",
},
},
}}
...
Dynamic import for loading the editor from client-side
// pages/index.js
import dynamic from "next/dynamic";
const Editor = dynamic(() => import("../components/editor"), {ssr: false})
To sum up:
Custom the CKEditor build, add needed plugins... then rebuild. Make them as a local package
Use that local package in our app!
Check my git sample with long comments: https://github.com/nghiaht/nextjs-ckeditor5

ReduxForm is rendered twice when mounted via enzyme

I'm trying to align my tests to follow breaking changes after upgrading react-redux to 6.0.0 and redux-form to 8.1.0 (connected components do not take store in props any longer)
I needed to wrap my connected component in from react-redux in tests and use mount to get to actual component but now ReduxForm is rendered twice.
I tried to use hostNodes() method but it returns 0 elements.
Any ideas how to fix it?
Here is the test:
import React from 'react'
import { mount } from 'enzyme'
import configureStore from 'redux-mock-store'
import { Provider } from 'react-redux'
import PasswordResetContainer from './PasswordResetContainer'
describe('PasswordResetContainer', () => {
it('should render only one ReduxForm', () => {
const mockStore = configureStore()
const initialState = {}
const store = mockStore(initialState)
const wrapper = mount(<Provider store={store}><PasswordResetContainer /></Provider>)
const form = wrapper.find('ReduxForm')
console.log(form.debug())
expect(form.length).toEqual(1)
})
And PasswordResetContainer looks like this:
import { connect } from 'react-redux'
import { reduxForm } from 'redux-form'
import PasswordReset from './PasswordReset'
import { resetPassword } from '../Actions'
export const validate = (values) => {
const errors = {}
if (!values.email) {
errors.email = 'E-mail cannot be empty.'
} else if (!/^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(values.email)) {
errors.email = 'Invalid e-mail.'
}
return errors
}
export default connect(null, { resetPassword })(
reduxForm(
{ form: 'passwordReset',
validate
})(PasswordReset))
Output from test is following:
PasswordResetContainer › should render only one ReduxForm
expect(received).toEqual(expected)
Expected value to equal:
1
Received:
2
Edit (partial solution found):
When I changed wrapper.find('ReduxForm')
into wrapper.find('ReduxForm>Hoc>ReduxForm') it started to work.
Why do I need to do such a magic?
A fix is on library mods to create but if the forms are identical, one quick way to get around the issue is to call first() after find so that
wrapper.find('ReduxForm')
looks like:
wrapper.find('ReduxForm').first()

Vue <template> in a .vue file with Lodash

In my .vue file within my template section I have:
<a v-bind:href="'javascript:artist(\'' + _.escape(artist) + '\')'">
which is using the Lodash function _.escape. This generates a string of errors the first of which is:
[Vue warn]: Property or method "_" is not defined on the instance but referenced during
render.
However in the same file in the script section of the component I am happily and successfully using a range of Lodash functions.
This is a Laravel app and in my app.js file I have this code:
require('./bootstrap');
window.Vue = require('vue');
import VueRouter from 'vue-router';
window.Vue.use(VueRouter);
import lodash from 'lodash';
Object.defineProperty(Vue.prototype, '$lodash', { value: lodash });
import SearchHome from './components/search.vue';
const routes = [
{
path: '/',
components: {
searchHome: SearchHome
}
},
]
const router = new VueRouter({ routes })
const app = new Vue({ router }).$mount('#app')
Can anyone please help me?
Try to use a computed value instead. This will improve readability.
Avoid complex operation in a binding.
<a :href="artistLink">
And in the script
import _ from 'lodash'
export default {
computed: {
artistLink () {
return 'javascript:artist(\'' + _.escape(this.artist) + '\')'
}
}
}

Resources