Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Vue custom preview template #2183

Open
kendallroth opened this issue Mar 15, 2019 · 30 comments
Open

Support Vue custom preview template #2183

kendallroth opened this issue Mar 15, 2019 · 30 comments

Comments

@kendallroth
Copy link

_Could not find any issues that dealt with this, with the possible exception of #1041.

Is your feature request related to a problem? Please describe.

When developing a Vue project utilizing Netlify CMS I cannot import the Vue components as a custom preview template, as I can a React component.

Describe the solution you'd like

It would be nice to import (Gridsome) Vue template components and register them as custom preview templates.

Describe alternatives you've considered

The proposed Handlebars-like syntax mentioned in the issue above may be the closest thing we can get?

Additional context

N/A

@erquhart
Copy link
Contributor

erquhart commented Apr 2, 2019

I'd love to support Vue templates for previews 👍 👍

That issue you linked is a prerequisite to allowing any kind of template to be used, as it provides an API for parsing various template types.

@pniedzwiedzinski

This comment has been minimized.

@stale
Copy link

stale bot commented Oct 29, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@tresko

This comment has been minimized.

2 similar comments
@em-jones

This comment has been minimized.

@ECAL-Technologies-Centre

This comment has been minimized.

@iBobik
Copy link

iBobik commented May 18, 2020

Would be possible to do it by https://github.com/akxcv/vuera

@DRBragg

This comment has been minimized.

@TorbjornHoltmon
Copy link

Has anyone tried to make it work with https://github.com/akxcv/vuera ?
+1

@pdkn

This comment has been minimized.

@devboldium

This comment has been minimized.

@nonlinearcom

This comment has been minimized.

1 similar comment
@gkweb

This comment has been minimized.

@erezrokah
Copy link
Contributor

Hi all, please upvote the issue by clicking the upvote button 👍 .
That way we can sort by upvotes when we search for issues.

@brainafesoh
Copy link

Hi guys, could anyone please provide a sample implementation using https://github.com/akxcv/vuera?

@luksak
Copy link

luksak commented Sep 10, 2021

Just tried to get this to work, but failed until now... Has anyone made progress on this?

@azriel46d
Copy link

I have managed to get it to work with something as follows (inspired a bit by Vuera)

  1. Created my React Compnent which is registered using registerPreviewTemplate. In it, there is a useEffect, which passes the ref and data to another function vueWrapperRef
const MyTemplate = ({ entry, getAsset }) => {
  const data = entry.getIn(["data"]).toJS();
  
  let ref = React.createRef()
  React.useEffect(() => {
    vueWrapperRef(ref, data) // --> THIS IS THE IMPORTANT PART
  },[data])
  if (data) {
    return <div ref={ref}></div>;
  } else {
    return <div>Loading...</div>;
  }
};

export default MyTemplate;
  1. The vueWrapperRef, creates the Vue component. The only way I managed to get it to be reactive is that the Data object is referenced and so when this function is recalled due to useEffect I update just the referenced Data and force the component to update.
let $vm
let DATA  = {} 
function vueWrapperRef(targetElement, data) {
    if (!$vm){
      DATA = data
      $vm = new Vue({
          el: targetElement.current, 
          render: function render(createElement) {
            return createElement(MyVueComponent, { // --> MyVueComponent can be even an imported .vue file
              props: DATA
            });
          },
        })
    } else {
      Object.keys(data).forEach(key => {
        DATA[key] = data[key]
      })
      $vm.$forceUpdate();
    }
}

@luksak
Copy link

luksak commented Dec 18, 2021

@azriel46d uh, great news! Could you share a repository with a working example? That would be helpful!

@luksak
Copy link

luksak commented Dec 18, 2021

Ok, I got it working in a very basic way. I had to disable lots of things, since I am using nuxt. Some nuxt features that are broken for me:

  • Autoloading components
  • All custom nuxt config of my app such as custom build configuration for css transpilation and bundling.

Does anyone know how to render a nuxt app programmatically in the preview? This could be a starting point:

https://nuxtjs.org/docs/internals-glossary/nuxt-render-route

What we'd need to figure out is how we can pass data to the nuxt app.

In nuxt 3 this might be quite easy:

https://v3.nuxtjs.org/docs/usage/nuxt-app#nuxtapp-interface-advanced

Another approach would be the nuxt preview mode could help: https://nuxtjs.org/docs/features/live-preview/ The advantage of this would be that the changes to the nuxt app would be minimal. But we'd need to provide nuxt with the data from the editor and that would be quite a challenge since we'd have to add that to the GET params, which isn't going to work for larger amounts of data.

I am hijacking this issue here. Should I create a separate for nuxt previews?

@mattickx
Copy link

How I've solved it in the passed for ANY framework:
Code a preview page in the framework you are using, expecting window.previewDataCMS to be fetched upwards (parent) of an Iframe.

In my netlify admin/index.html:

<script>
  window.previewDataCMS = {};
  const GeneratePreview = (type, properties = []) => createClass({
    render: function () {
      const { entry } = this.props;
      const data = { type };
      const keys = (properties || []);
      if (Array.isArray(keys)) {
        keys.forEach((key) => {
          try {
            const tmp = entry.getIn(['data', key]);
            data[key] = tmp === 'undefined' ? '' : tmp;
          } catch (e) {
            console.log(e);
          }
        })
        try {
          data.mediaFiles = entry.get('mediaFiles')
        } catch (e) {
          console.log(e);
        }
      }
      let host = window.location.host;
      const isLocal = ['localhost', '127.0.0.1', '0.0.0.0'].includes((host || 'default-online').split(':')[0]);
      const src = `${isLocal ? 'http' : 'https'}://${host}/preview`;
      window.previewDataCMS = data;
      const html = `<iframe border="0" src="${src}" width="100%" height="100%" style="border: 1px solid #EEE; height: calc(100vh - 80px)"></iframe>`;
      return h('div', { dangerouslySetInnerHTML: { __html: html } });
    },
  });
  CMS.registerPreviewTemplate('blog', GeneratePreview('blog', ['title', 'date', 'intro', 'category', 'content']));
  CMS.registerPreviewTemplate('poses', GeneratePreview('poses', ['poses']));
  CMS.registerPreviewTemplate('colors', GeneratePreview('colors', ['title', 'image']));
</script>

In my Vue preview page:

setInterval(() => {
  let previewData = {};
  let current = window;
  let n = 0;
  while (current.parent && n < 10) {
    current = current.parent;
    n += 1;
    if (current.previewDataCMS) {
      previewData = current.previewDataCMS;
    }
  }
  this.previewData = JSON.parse(JSON.stringify(previewData));
}, 1000);

// this.previewData is accessible ...

This setup can be used for any framework as the data is just being fetched from the parent of the iframe.

Note: This worked in my time of using Netlify CMS, which might not be the same version currently.
I was using: https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js

Hope it helps anyone! 👍

@luksak
Copy link

luksak commented Dec 19, 2021

@mathieumagalhaes Thanks a lot for your suggestion! Ha, this is a very weird approach. Quite mind blowing that this actually works... But i love that the only thing needed is an iframe and that it doesn't matter what technology is being used in the preview.

My nuxt app is showing the data for the editor in real time. I'll share code here once i am ready. I still need to clean up some things...

@mattickx
Copy link

mattickx commented Dec 19, 2021

@mathieumagalhaes Thanks a lot for your suggestion! Ha, this is a very weird approach. Quite mind blowing that this actually works... But i love that the only thing needed is an iframe and that it doesn't matter what technology is being used in the preview.

My nuxt app is showing the data for the editor in real time. I'll share code here once i am ready. I still need to clean up some things...

Basic HTML and javascript, glad it helps

@solidevolution
Copy link

@mathieumagalhaes great solution, thanks!

@letoast
Copy link

letoast commented Dec 7, 2022

Note: This worked in my time of using Netlify CMS, which might not be the same version currently. I was using: https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js

Hope it helps anyone! 👍

So I'm working with Sveltekit and after a couple of tries I got this to work. I had to change a few things in your code to make it work with the newest version of Netlify CMS, though.

First, the function createClass isn't available by default, so I had to install and import:

import createClass from 'create-react-class';

Secondly, the function h isn't available as well, but it does the same thing as React's createElement, so I had to import and use that instead of h

import React from 'react';
...
const element = React.createElement('div', { dangerouslySetInnerHTML: { __html: html } });

In Sveltekit I created a new page called Preview, where I import other pages as components and feed the window data into them:

<script lang="ts">
	import Homepage from '../+page.svelte';
	import { browser } from '$app/environment';

	let content: any;

	if (browser) {
		setInterval(() => {
			content = window.parent.parent.previewDataCMS;
		}, 1000);
	}
</script>

<svelte:component this={Homepage} data={content} />

I also added a +layout.server.ts in the same folder as the preview page that has this content:

export const prerender = 'false'

so that this page doesn't get prerendered. That's it!

Thanks again!

@letoast
Copy link

letoast commented Dec 8, 2022

One more thing, to get all of the data available in the collection i replaced this part:

const data = { type };
const keys = (properties || []);
if (Array.isArray(keys)) {
  keys.forEach((key) => {
    try {
      const tmp = entry.getIn(['data', key]);
      data[key] = tmp === 'undefined' ? '' : tmp;
    } catch (e) {
      console.log(e);
    }
  })
  try {
    data.mediaFiles = entry.get('mediaFiles')
  } catch (e) {
    console.log(e);
  }
}
window.previewDataCMS = data;

with:

window.previewDataCMS = entry.get('data').toJSON();

@aminimalanimal
Copy link

@mathieumagalhaes Thank you so much for your comment here. You helped me save one of my projects, as the lack of live previews in our CMS was going to force our hand to change the stack.

@luksak
Copy link

luksak commented Jul 26, 2023

There is only one very important feature missing: Preview of new files that have not yet been saved. Does that work in the react preview?

@mattickx
Copy link

One more thing:
...

window.previewDataCMS = entry.get('data').toJSON();

@letoast I remember looking for a helper function that did just that but couldn't found one back then. Great that it exists now.

Thank you so much for your comment here. You helped me save one of my projects, as the lack of live previews in our CMS was going to force our hand to change the stack.

@aminimalanimal My pleasure, I only hope Decap will actively maintain and start developing this project further.

There is only one very important feature missing: Preview of new files that have not yet been saved. Does that work in the react preview?

@luksak That worked in my example, while editing (even for new files) the data refreshes thanks to the setTimeout.
I do not think that broke since then but I haven't checked.

@luksak
Copy link

luksak commented Jul 28, 2023

@mathieumagalhaes alright, I'm gonna test that.

But I wonder how that would work. Is the data available as a blob? That would at least involve some custom code in a component rendering an <img> tag.

Are you sure it works for you?

@luksak
Copy link

luksak commented Aug 5, 2023

@mathieumagalhaes I just researched this again. This is not working out of the box for sure.

But there is getAsset being passed to the preview component that makes it possible to get the data of new uploaded files.

I know this is far from clean code, but I'm just not familiar enough with React to solve this properly:

import React from 'react';

const Preview = ({ entry, getAsset }) => {

  function replaceAssets(data) {
    Object.keys(data).forEach(key => {
      if (key === 'src') {
        data[key] = getAsset(data[key]).url
      }
      else if (typeof data[key] === 'object') {
        data[key] = replaceAssets(data[key])
      }
    });
    return data;
  }

  let src = '';
  let host = window.location.host;

  const localHostnames = ['localhost', '127.0.0.1', '0.0.0.0']
  if (localHostnames.includes(window.location.hostname)) {
    src = `http://${window.location.hostname}:3002/preview/`;
  } else {
    src = `https://${host}/`;
  }
  src += entry.toJS().slug
  src += '?preview=true'
  window.previewDataCMS = replaceAssets(entry.toJS().data);
  const html = `<iframe border="0" src="${src}" width="100%" height="100%" style="border: 1px solid #EEE; height: calc(100vh - 80px)"></iframe>`;
  return <div dangerouslySetInnerHTML={{ __html: html }} />;
};

export default Preview

The replaceAssets function assumes that the files have the key src. Those values are being replace recursively in the data and new images are displayed using this in my nuxt app preview.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests