Add preview handling

This commit is contained in:
nicwands
2026-06-01 11:08:03 -04:00
parent ef70a0c756
commit 6776a14757
18 changed files with 1186 additions and 14 deletions

View File

@@ -0,0 +1,64 @@
<template>
<!-- This component is invisible but handles Live Preview communication -->
<div style="display: none"></div>
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue'
const STRAPI_ORIGIN = 'https://cms.takerofnotes.com'
/**
* Handle messages from Strapi's Live Preview
*/
const handleMessage = async (message) => {
const { origin, data } = message
// Only accept messages from our Strapi instance
if (origin !== STRAPI_ORIGIN) {
return
}
try {
switch (data.type) {
case 'strapiUpdate':
// Content has been updated in Strapi, refresh the page to show changes
console.log('Strapi content updated, refreshing preview...')
window.location.reload()
break
case 'strapiScript':
// Inject the Live Preview script for interactive editing
console.log('Injecting Live Preview script...')
const script = document.createElement('script')
script.textContent = data.payload.script
document.head.appendChild(script)
break
default:
// Unknown message type, ignore
break
}
} catch (error) {
console.error('Error handling Live Preview message:', error)
}
}
onMounted(() => {
// Add event listener for messages from Strapi
window.addEventListener('message', handleMessage)
// Signal to Strapi that we're ready to receive Live Preview scripts
try {
window.parent?.postMessage({ type: 'previewReady' }, STRAPI_ORIGIN)
console.log('Live Preview: Signaled ready to parent window')
} catch (error) {
console.error('Error signaling Live Preview readiness:', error)
}
})
onUnmounted(() => {
// Clean up event listener
window.removeEventListener('message', handleMessage)
})
</script>

View File

@@ -0,0 +1,106 @@
<template>
<div v-if="isPreview" class="preview-banner theme-dark">
<div class="preview-banner__content">
<div class="preview-banner__info">
<span class="preview-banner__label">Preview Mode</span>
<span class="preview-banner__status">{{ status }}</span>
</div>
<a
:href="exitPreviewUrl"
class="preview-banner__exit"
@click="exitPreview"
>
Exit Preview
</a>
</div>
</div>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
isPreview: {
type: Boolean,
default: false,
},
status: {
type: String,
default: 'published',
},
})
const exitPreviewUrl = computed(() => {
if (typeof window === 'undefined') return '/'
const url = new URL(window.location.href)
url.searchParams.delete('preview')
url.searchParams.delete('status')
url.searchParams.delete('documentId')
url.searchParams.delete('uid')
return url.toString()
})
const exitPreview = (event) => {
// For better UX, we can use client-side navigation instead of full page reload
event.preventDefault()
if (typeof window !== 'undefined') {
window.location.href = exitPreviewUrl.value
}
}
</script>
<style lang="scss" scoped>
.preview-banner {
position: fixed;
top: 0;
left: 0;
right: 0;
background: var(--theme-accent);
color: var(--theme-bg);
z-index: 9999;
padding: 8px 16px;
&__content {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
font-size: 14px;
font-weight: 500;
}
&__info {
display: flex;
align-items: center;
gap: 12px;
}
&__label {
font-weight: 600;
}
&__status {
background: rgba(255, 255, 255, 0.2);
padding: 2px 8px;
border-radius: 12px;
font-size: 12px;
text-transform: capitalize;
}
&__exit {
color: var(--theme-bg);
text-decoration: none;
padding: 4px 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 4px;
transition: all 0.2s ease;
&:hover {
background: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.5);
}
}
}
</style>