stuck while implementing [this quill to pdf][1] package to my laravel vue3 project. It is working fine to use, but when i try to save the rich text as pdf it i am getting stucked.
<form #submit.prevent="store">
<div class="flex flex-wrap -mb-8 -mr-6 p-8">
<text-input v-model="form.title" :error="form.errors.title" class="pb-8 pr-6 w-full lg:w-1/2" label="title" />
</div>
<div class="-mb-8 -mr-6 p-8 h-100">
<QuillEditor theme="snow" :toolbar="modules.toolbar" v-model:content="form.content" />
</div>
<div class="flex items-center justify-end px-8 py-4 bg-gray-50 border-t border-gray-100">
<loading-button :loading="form.processing" class="btn-indigo" type="submit">Create Organization</loading-button>
</div>
</form>
<button #click="saveAsFile()" class="btn-indigo">Get Pdf</button>
and vue js goes:
data() {
return {
loading: true,
form: this.$inertia.form({
title: null,
content: null,
}),
}
},
setup: () => {
//setTimeout(true, 4000)
var toolbarOptions = [['bold', 'italic'], ['link', 'image']];
const modules = {
name: 'blotFormatter',
toolbar: toolbarOptions,
module: BlotFormatter,
options: {/* options */}
}
return { modules }
},
methods: {
store() {
this.form.post('/templates')
},
async saveAsFile(){
const quillInstance = new Quill()
const delta = quillInstance.getContents(this.form.content); // gets the Quill delta
const pdfAsBlob = await pdfExporter.generatePdf(delta); // converts to PDF
saveAs(pdfAsBlob, 'pdf-export.pdf'); // downloads from the browser
}
},
i keep getting the error saying "Invalid Quill container" help me out please if you can!
Related
I am making a website using Next.js and the above error is shown every time.
Don't know what is wrong in my code.
const Login = () => {
const [userMsg, setUserMsg] = useState("");
const [email, setEmail] = useState("");
const router=useRouter();
const handleOnChangeEmail = (e) => {
e.preventDefault();
setUserMsg("");
console.log("event", e);
const email = e.target.value;
setEmail(email);
};
const handleLoginWithEmail = (e) => {
e.preventDefault();
if (email) {
if (IsEmail.validate(email)){
router.push("/")
}else{
setUserMsg("Enter a valid email address")
}
} else {
//show usermssg
setUserMsg("Enter an email address");
}
};
return (
<div className="bg-[url('/static/bglg.jpg')] flex items-stretch flex-col h-screen w-full">
<head>
<title>NeoVest SignIn</title>
</head>
<header className="text-4xl px-10 py-2 font-black">
<span className="text-indigo-700">NeoVest</span>
</header>
<div className="w-full max-w-xs m-auto bg-[#C9C9C9] rounded p-5 bg-opacity-50 border-gray-200">
<header>
<div className="text-indigo-700 font-black text-3xl py-2">
<p>Sign In</p>
</div>
</header>
<form className="py-5">
<div>
<label className="block mb-2 text-indigo-500" for="username">
Email
</label>
<input
className="w-full p-2 mb-6 text-indigo-700 border-b-2 border-indigo-500 outline-none focus:bg-gray-300"
type="text"
name="username"
placeholder="Email Address"
onChange={handleOnChangeEmail}
/>
<div className="block mb-2 text-red-700">
<p>{userMsg}</p>
</div>
</div>
<div>
<input
className="w-full bg-indigo-700 hover:bg-pink-700 text-white font-bold py-2 px-4 mb-6 rounded"
type="button"
value="Submit"
onClick={handleLoginWithEmail}
/>
</div>
</form>
</div>
</div>
);
};
Another error shown is due to some suspense boundary causing root to switch to client side rendering :
Error: There was an error while hydrating. Because the error happened
outside of a Suspense boundary, the entire root will switch to client
rendering.
I am also using Tailwind if that information is important.
If you would look at the console you would see a warning.
Warning: validateDOMNesting(...): <head> cannot appear as a child of <div>
So, to fix this you just have to move head out of the div and move it to a different higher component.
if you have this Warning in chrome:
> `validateDOMNesting(...): <head> cannot appear as a child of <div>`
Your DOM Tree is not printed in the browser correctly, one or more tags are not closed properly.
The problem is this:
<head>
<title>NeoVest SignIn</title>
</head>
To solve it, first import Head, and then, use it with Capital "H"
import Head from "next/head"
<Head>
<title>NeoVest SignIn</title>
</Head>
I verified some topics in my code. First, verified if in next.config.js was styled components declared.
reactStrictMode: true,
compiler: {
styledComponents: true,
},
In second, I checked if file babel ".babelrc" was created in root folder with this content:
{
"presets": ["next/babel"],
"plugins": ["styled-components"]
}
The error yet showed. Component by component I checked and more two errors was found. First, HTML errors like this:
<!-- wrong -->
<p>
<ul></ul>
</p>
<!-- right -->
<p></p>
<ul></ul>
<p></p>
<!-- or right -->
<div>
<ul></ul>
</div>
Finally, I found an error in initializing component. In my case, I declared - incorrectly - a const items (object). The right way to execute this is using useState.
<!-- WRONG WAY THAT I WAS DID -->
const Works = () => {
const items = [
{
id: 1,
name: 'any name',
src: 'srcpath'
},
{
id: 2,
name: 'any name 2',
src: 'srcpath'
},
{
id: 3,
name: 'any name 3',
src: 'srcpath'
},
];
return(
items.map((item, index) => {
return(
<div key={index}>{item.name}</div>
)
}
)
});
<!-- RIGHT WAY THAT I FIXED -->
const Works = () => {
const [ item, setItem ] = React.useState();
React.useEffect(() => {
setItem([
{
id: 1,
name: 'any name',
src: 'srcpath'
},
{
id: 2,
name: 'any name 2',
src: 'srcpath'
},
{
id: 3,
name: 'any name 3',
src: 'srcpath'
},
]);
}, []);
if(item)
return(
items.map((item, index) => {
return(
<div key={index}>{item.name}</div>
)
}
)
});
Time and randomness are two of the things that most commonly produce
this
as discussed in Hydration errors - Text content does not match server-rendered HTML. #38263
Possible solution might be to use useEffect hook
// Example component with an error
import React from "react";
const AllNames = ["Ali", "Elisa", "Bella", "Carmen"];
const RandomUniqueNames = Array.from({ length: 4 }).map((_, index) => {
let randomIndex = Math.floor(Math.random() * AllNames.length);
console.log(randomIndex);
let name = AllNames[randomIndex];
return {
name,
};
});
export default function ModernNames() {
return (
<div>
<h1>ModernNames</h1>
{RandomUniqueNames.map((name) => (
<p>{name.name}</p>
))}
</div>
);
}
// Example solution
import React, { useState, useEffect } from "react";
const AllNames = ["Ali", "Elisa", "Bella", "Carmen"];
export default function ModernNames() {
const [randomNames, setRandomNames] = useState([]);
useEffect(() => {
const RandomUniqueNames = Array.from({ length: 4 }).map((_, index) => {
let randomIndex = Math.floor(Math.random() * AllNames.length);
console.log(randomIndex);
let name = AllNames[randomIndex];
return {
name,
};
});
setRandomNames(RandomUniqueNames);
}, []);
return (
<div>
<h1>ModernNames</h1>
{randomNames.map((name, index) => (
<p key={index}>{name.name}</p>
))}
</div>
);
}
You can try downgrading react version. Try react#17.
I'm using snipcart in a nextjs with strapi app.
I receive an error when processing a payment :
A 'cart-confirmation' error occured in Snipcart. Reason:
'product-crawling-failed'
[Details] We have not been able to find item with id '59' at
'https://cmeditions.fr/products/selfless'. Please make sure the
product is correctly defined.
and :
An attempt to create an order with invalid products has been made.
Don't forget you can't add/update items attributes in the cart
dynamically with javascript as it will cause crawling issues
It works when data-item-id is replaced by a static value but not with somthing like {product.id}
Here is my component [sulg].js :
const ProductPage = ({ product }) => {
const router = useRouter()
if (router.isFallback) {
return <div>Loading product...</div>
}
return (
<div className="carousel-container m-6 grid grid-cols-1 gap-4 mt-8 items-start">
<Head>
<title>{product.title}</title>
</Head>
<div className="rounded-t-lg pt-2 pb-2 m-auto h-auto">
<EmblaCarousel {...product} />
</div>
<div className="w-full p-5 flex flex-col justify-between">
<div>
<h4 className="mt-1 font-semibold leading-tight truncate text-slate">
<span className="uppercase text-lg font-bold">{product.title}</span>
<br />
{product.author}
</h4>
<LanguageDesc {...product} />
</div>
<div className="flex flex-col md:flex-row">
<div className="mt-4 font-semibold leading-tight truncate text-slate">
<p>{product.year}</p>
<p>Format: {product.size}</p>
<p>Printing: {product.technic}</p>
<p>Paper: {product.medium}</p>
<p>{product.page}</p>
<p>{product.copies} copies</p>
<p>{product.price}€</p>
</div>
{product.status === "published" ? (
<button
className="snipcart-add-item mt-4 ml-16 self-center bg-slate border border-gray-200 d hover:shadow-lg hover:bg-brique focus:outline-none text-white font-semibold leading-none py-2 px-4 w-20 h-20 rounded-full"
data-item-id={product.id}
data-item-price={product.price}
data-item-url={router.asPath}
data-item-description={product.description}
data-item-image={getStrapiMedia(
product.image.formats.thumbnail.url
)}
data-item-name={product.title}
v-bind="customFields"
>
Add to cart
</button>
) : (
<div className="text-center mr-10 mb-1" v-else>
<div
className="p-2 bg-indigo-800 items-center text-indigo-100 leading-none lg:rounded-full flex lg:inline-flex"
role="alert"
>
<span className="flex rounded-full bg-indigo-500 uppercase px-2 py-1 text-xs font-bold mr-3">
Coming soon...
</span>
<span className="font-semibold mr-2 text-left flex-auto">
This article is not available yet.
</span>
</div>
</div>
)}
</div>
</div>
</div>
)
}
export async function getStaticProps({ params }) {
const product = await getProduct(params.slug)
return { props: { product } }
}
export async function getStaticPaths() {
const products = await getProducts()
return {
paths: products.map((_product) => {
return {
params: { slug: _product.slug },
}
}),
fallback: true,
}
}
export default ProductPage
EDIT
I tried to return a json to the validator, but I think it isn't right
/api/products.js
import { getProducts } from "../../utils/api"
// eslint-disable-next-line import/no-anonymous-default-export
export default async function (_req, res) {
return new Promise((resolve) => {
getProducts()
.then((response) => {
res.statusCode = 200
res.setHeader("Content-Type", "application/json")
res.setHeader("Cache-Control", "max-age=180000")
res.end(JSON.stringify(response))
resolve()
})
.catch((error) => {
res.json(error)
res.status(405).end()
resolve() // in case something goes wrong in the catch block (as vijay commented)
})
})
}
api.js
export function getStrapiURL(path) {
return `${
process.env.NEXT_PUBLIC_STRAPI_API_URL || "http://localhost:1337"
}${path}`
}
// Helper to make GET requests to Strapi
export async function fetchAPI(path) {
const requestUrl = getStrapiURL(path)
const response = await fetch(requestUrl)
const data = await response.json()
return data
}
export async function getCategories() {
const categories = await fetchAPI("/categories")
return categories
}
export async function getCategory(slug) {
const categories = await fetchAPI(`/categories?slug=${slug}`)
return categories?.[0]
}
export async function getProducts() {
const products = await fetchAPI("/products")
return products
}
export async function getProduct(slug) {
const products = await fetchAPI(`/products?slug=${slug}`)
return products?.[0]
}
export async function getLibrairies() {
const librairies = await fetchAPI("/librairies")
return librairies
}
export async function getLibrairie(slug) {
const librairies = await fetchAPI(`/librairies?slug=${slug}`)
return librairies?.[0]
}
It sounds like you will need to validate the orders with a JSON file that you can assign to the buy button with data-item-url.
You can read the full guide here:
https://docs.snipcart.com/v3/setup/order-validation#json-crawler.
Let me know if that helps!
It finally wasn't a matter of JSON crawler but something to see with the domain on the snipcart dashboard. I had to put the vercel.app domain first and my own domain name as a subdomain. It worked when I changed this. This is the only explaination I got
I wrote a graphql query to get recent posts for a blog app in NextJs in other to display them as widgets
export const getRecentPosts = async () =\> {
const query = gql`
query MyQuery {
posts(orderBy: createdAt_ASC
last: 3 )
{title
featuredImage {url}
createdAt
slug
}
}
`;
const result = await request(graphAPI, query);
return result.posts;
};
but i keep getting a "Cannot read properties of undefined (reading 'document')" error
and it keeps displaying this error
NextJs error
I imported the function into the component
function PostWidget({ categories, slug }) {
const [relatedPosts, setRelatedPosts] = useState([]);
useEffect(() => {
if (slug) {
getSimilarPosts(categories, slug).then((result) => {
setRelatedPosts(result);
});
} else {
getRecentPosts().then((result) => {
setRelatedPosts(result);
});
}
}, [slug]);
console.log(relatedPosts)
return (
<div className="bg-white shadow-lg rounded-lg p-8 mb-8">
<h3 className="text-xl mb-4 font-semibold border-b p-4">
{slug ? "Related posts" : "Recent posts"}
</h3>
{
relatedPosts.map((post) => (
<div key={post.title} className="flex items-center w-full mb-4">
<div className="w-16 flex-none">
<img
alt={post.title}
height="60px"
width="60px"
className="align-middle rounded-full"
src={post.featuredImage.url}
/>
</div>
</div>
))
}
</div>
)
}
but it gives an empty array when i console.log the related posts
I want to write my blog post with markdown editor, my problem is when I publish my post edit with markdown then code block, list item, table, and so on not working properly, another part is works file like image, link, text bold...
looks like:
And output is:
I have tried code by this way:
posts.js file
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { sortByDate } from '#/utils/index'
const files = fs.readdirSync(path.join('posts'))
export function getPosts() {
const posts = files.map((filename) => {
const slug = filename.replace('.md', '')
const markdownWithMeta = fs.readFileSync(
path.join('posts', filename),
'utf-8'
)
const { data: frontmatter } = matter(markdownWithMeta)
return {
slug,
frontmatter,
}
})
return posts.sort(sortByDate)
}
[slug.js] file:
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import marked from 'marked'
import Link from 'next/link'
import Layout from '#/components/Layout'
import CategoryLabel from '#/components/CategoryLabel'
export default function PostPage({
frontmatter: { title, category, date, cover_image, author, author_image },
content,
slug,
}) {
return (
<Layout title={title}>
<Link href='/blog'>Go Back</Link>
<div className='w-full px-10 py-6 bg-white rounded-lg shadow-md mt-6 md:grid-cols-6'>
<div className='flex justify-between items-center mt-4'>
<h1 className='text-5xl mb-7'>{title}</h1>
<CategoryLabel>{category}</CategoryLabel>
</div>
<img src={cover_image} alt='' className='w-full rounded' />
<div className='flex justify-between items-center bg-gray-100 p-2 my-8'>
<div className='flex items-center'>
<img
src={author_image}
alt=''
className='mx-4 w-10 h-10 object-cover rounded-full hidden sm:block'
/>
<h4>{author}</h4>
</div>
<div className='mr-4'>{date}</div>
</div>
<div className='blog-text mt-2'>
<div dangerouslySetInnerHTML={{ __html: marked(content) }}></div>
</div>
</div>
</Layout>
)
}
export async function getStaticPaths() {
const files = fs.readdirSync(path.join('posts'))
const paths = files.map((filename) => ({
params: {
slug: filename.replace('.md', ''),
},
}))
return {
paths,
fallback: false,
}
}
export async function getStaticProps({ params: { slug } }) {
const markdownWithMeta = fs.readFileSync(
path.join('posts', slug + '.md'),
'utf-8'
)
const { data: frontmatter, content } = matter(markdownWithMeta)
return {
props: {
frontmatter,
content,
slug,
},
}
}
Any suggestion please.
See: https://nextjs.org/blog/markdown
You need to setup: https://github.com/tailwindlabs/tailwindcss-typography
Then add the prose class to your div that contains the markdown
<div className='blog-text prose mt-2'>
<div dangerouslySetInnerHTML={{ __html: marked(content) }}></div>
</div>
I'm trying to build a toast notification component in Alpine.js. But I'm not getting to send notification from vanilla js files.
<div
x-data="
{
notices: [],
visible: [],
add(notice) {
notice.id = Date.now()
this.notices.push(notice)
this.fire(notice.id)
},
fire(id) {
this.visible.push(this.notices.find(notice => notice.id == id))
const timeShown = 10000 * this.visible.length
setTimeout(() => {
this.remove(id)
}, timeShown)
},
remove(id) {
const notice = this.visible.find(notice => notice.id == id)
const index = this.visible.indexOf(notice)
this.visible.splice(index, 1)
},
}
"
class="z-50 p-7 fixed inset-0 w-screen flex flex-col-reverse items-end justify-end pointer-events-none"
#notice.window="add($event.detail)">
<template x-for="notice of notices" :key="notice.id">
<div
x-show="visible.includes(notice)"
x-transition:enter="transition ease-in duration-400"
x-transition:enter-start="transform opacity-0 translate-x-full"
x-transition:enter-end="transform opacity-100"
x-transition:leave="transition ease-out duration-500"
x-transition:leave-start="transform translate-x-0 opacity-100"
x-transition:leave-end="transform translate-x-full opacity-0"§
#click="remove(notice.id)"
class="rounded mb-4 p-3 w-full max-w-md text-green-800 shadow-lg cursor-pointer pointer-events-auto bg-green-200"
x-text="notice.text">
</div>
</template>
</div>
https://codepen.io/nuno360/pen/WNGKmeL
From vanilla js file how I can send notices to this component?
You can create a custom event and dispatch it (assuming you're selecting an element of some sort) with element.dispatchEvent(new CustomEvent('notice', { detail: {}, bubbles: true }))