Integration pages from Notion (#753)
* Integration pages from Notion * fix integration page * Refactor environment variables and update routing in integration pages - Removed unused environment variables related to MUX from `.env.docker` and `.env.example` to streamline configuration. - Updated routing links in `OpenFormFooter.vue` to correct navigation paths for "Integrations", "Report Abuse", and "Privacy Policy", enhancing user experience. - Added middleware to `definePageMeta` in `index.vue` and `[slug].vue` for integration pages to enforce self-hosted logic. These changes aim to improve code clarity and ensure proper routing functionality across the application. --------- Co-authored-by: Julien Nahum <julien@nahum.net>
This commit is contained in:
@@ -1,7 +1,18 @@
|
||||
<template>
|
||||
<notion-renderer
|
||||
<NotionRenderer
|
||||
v-if="!loading"
|
||||
:block-map="blockMap"
|
||||
:block-map="blocks"
|
||||
:block-overrides="blockOverrides"
|
||||
:content-id="contentId"
|
||||
:full-page="fullPage"
|
||||
:hide-list="hideList"
|
||||
:level="level"
|
||||
:map-image-url="mapImageUrl"
|
||||
:map-page-url="mapPageUrl"
|
||||
:page-link-options="pageLinkOptions"
|
||||
:image-options="imageOptions"
|
||||
:prism="prism"
|
||||
:todo="todo"
|
||||
/>
|
||||
<div
|
||||
v-else
|
||||
@@ -12,27 +23,69 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NotionRenderer } from "vue-notion"
|
||||
import { NotionRenderer, defaultMapPageUrl, defaultMapImageUrl } from 'vue-notion'
|
||||
|
||||
export default {
|
||||
name: "NotionPage",
|
||||
name: 'NotionPage',
|
||||
components: { NotionRenderer },
|
||||
props: {
|
||||
blockMap: {
|
||||
type: Object
|
||||
},
|
||||
blockOverrides: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
type: Boolean
|
||||
},
|
||||
contentId: String,
|
||||
contentIndex: { type: Number, default: 0 },
|
||||
fullPage: { type: Boolean, default: false },
|
||||
hideList: { type: Array, default: () => [] },
|
||||
level: { type: Number, default: 0 },
|
||||
mapImageUrl: { type: Function, default: defaultMapImageUrl },
|
||||
mapPageUrl: { type: Function, default: defaultMapPageUrl },
|
||||
pageLinkOptions: {
|
||||
type: Object, default: () => {
|
||||
const NuxtLink = resolveComponent('NuxtLink')
|
||||
return { component: NuxtLink, href: 'to' }
|
||||
}
|
||||
},
|
||||
imageOptions: Object,
|
||||
prism: { type: Boolean, default: false },
|
||||
todo: { type: Boolean, default: false }
|
||||
},
|
||||
computed: {
|
||||
blocks () {
|
||||
if (this.blockMap && this.blockMap.data) {
|
||||
return this.blockMap.data
|
||||
}
|
||||
return this.blockMap
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang='scss'>
|
||||
@import "vue-notion/src/styles.css";
|
||||
|
||||
.notion-blue {
|
||||
@apply text-nt-blue;
|
||||
}
|
||||
|
||||
.notion-spacer {
|
||||
width: 24px !important;
|
||||
}
|
||||
|
||||
.notion-link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.notion {
|
||||
img, iframe {
|
||||
@apply rounded-md;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -50,6 +50,18 @@
|
||||
Technical Docs
|
||||
</a>
|
||||
<template v-if="!useFeatureFlag('self_hosted')">
|
||||
<router-link
|
||||
:to="{ name: 'integrations' }"
|
||||
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
|
||||
>
|
||||
Integrations
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{ name: 'report-abuse' }"
|
||||
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
|
||||
>
|
||||
Report Abuse
|
||||
</router-link>
|
||||
<router-link
|
||||
:to="{ name: 'privacy-policy' }"
|
||||
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
|
||||
@@ -63,13 +75,6 @@
|
||||
>
|
||||
Terms & Conditions
|
||||
</router-link>
|
||||
|
||||
<router-link
|
||||
:to="{ name: 'report-abuse' }"
|
||||
class="text-gray-600 dark:text-gray-400 transition-colors duration-300 hover:text-nt-blue"
|
||||
>
|
||||
Report Abuse
|
||||
</router-link>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
68
client/components/pages/notion/CustomBlock.vue
Normal file
68
client/components/pages/notion/CustomBlock.vue
Normal file
@@ -0,0 +1,68 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="innerJson"
|
||||
id="custom-block"
|
||||
>
|
||||
<div
|
||||
v-if="innerJson.type=='faq'"
|
||||
class="rounded-lg bg-white z-10 pt-10"
|
||||
>
|
||||
<h2 class="font-medium">
|
||||
Frequently Asked Questions
|
||||
</h2>
|
||||
<dl class="pt-4 space-y-6">
|
||||
<div
|
||||
v-for="question in innerJson.content"
|
||||
:key="question.label"
|
||||
class="space-y-2"
|
||||
>
|
||||
<dt class="font-semibold text-gray-900 dark:text-gray-100">
|
||||
{{ question.label }}
|
||||
</dt>
|
||||
<dd
|
||||
class="leading-6 text-gray-600 dark:text-gray-400"
|
||||
v-html="question.content"
|
||||
/>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="innerJson.type=='cta'"
|
||||
class="rounded-lg relative bg-gradient-to-r from-blue-400 to-blue-600 shadow-ld p-8 z-10"
|
||||
>
|
||||
<div class="absolute inset-px rounded-[calc(var(--radius)-1px)]">
|
||||
<div class="flex justify-center w-full h-full">
|
||||
<SpotlightCard
|
||||
class="w-full p-2 rounded-[--radius] [--radius:theme(borderRadius.lg)] opacity-70"
|
||||
from="#60a5fa"
|
||||
:size="200"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative z-20 flex flex-col items-center gap-4 pb-1">
|
||||
<h2 class="text-xl md:text-2xl text-center font-medium text-white">
|
||||
{{ innerJson.title ? innerJson.title : 'Ready to upgrade your OpnForm forms?' }}
|
||||
</h2>
|
||||
<UButton
|
||||
to="/register"
|
||||
color="white"
|
||||
class="hover:no-underline"
|
||||
icon="i-heroicons-arrow-right"
|
||||
trailing
|
||||
>
|
||||
Try OpnForm for free
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { blockProps } from 'vue-notion'
|
||||
import useNotionBlock from '~/components/pages/notion/useNotionBlock.js'
|
||||
|
||||
const props = defineProps(blockProps)
|
||||
|
||||
const block = useNotionBlock(props)
|
||||
const innerJson = computed(() => block.innerJson.value)
|
||||
</script>
|
||||
107
client/components/pages/notion/useNotionBlock.js
vendored
Normal file
107
client/components/pages/notion/useNotionBlock.js
vendored
Normal file
@@ -0,0 +1,107 @@
|
||||
import { computed } from 'vue'
|
||||
|
||||
export default function useNotionBlock (props) {
|
||||
|
||||
const block = computed(() => {
|
||||
const id = props.contentId || Object.keys(props.blockMap)[0]
|
||||
return props.blockMap[id]
|
||||
})
|
||||
|
||||
const value = computed(() => {
|
||||
return block.value?.value
|
||||
})
|
||||
|
||||
const format = computed(() => {
|
||||
return value.value?.format
|
||||
})
|
||||
|
||||
const icon = computed(() => {
|
||||
return format.value?.page_icon || ''
|
||||
})
|
||||
|
||||
const width = computed(() => {
|
||||
return format.value?.block_width
|
||||
})
|
||||
|
||||
const properties = computed(() => {
|
||||
return value.value?.properties
|
||||
})
|
||||
|
||||
const caption = computed(() => {
|
||||
return properties.value?.caption
|
||||
})
|
||||
|
||||
const description = computed(() => {
|
||||
return properties.value?.description
|
||||
})
|
||||
|
||||
const src = computed(() => {
|
||||
return mapImageUrl(properties.value?.source[0][0], block.value)
|
||||
})
|
||||
|
||||
const title = computed(() => {
|
||||
return properties.value?.title
|
||||
})
|
||||
|
||||
const alt = computed(() => {
|
||||
return caption.value?.[0][0]
|
||||
})
|
||||
|
||||
const type = computed(() => {
|
||||
return value.value?.type
|
||||
})
|
||||
|
||||
const visible = computed(() => {
|
||||
return !props.hideList.includes(type.value)
|
||||
})
|
||||
|
||||
const hasPageLinkOptions = computed(() => {
|
||||
return props.pageLinkOptions?.component && props.pageLinkOptions?.href
|
||||
})
|
||||
|
||||
const parent = computed(() => {
|
||||
return props.blockMap[value.value?.parent_id]
|
||||
})
|
||||
|
||||
const innerJson = computed(() => {
|
||||
if (type.value !== 'code') return
|
||||
if (properties.value.language.flat('Infinity').join('') !== 'JSON') {
|
||||
return
|
||||
}
|
||||
try {
|
||||
return JSON.parse(
|
||||
title.value.flat(Infinity).join('').replace(/\n/g, '').replace(/\t/g, '').trim()
|
||||
)
|
||||
} catch (error) {
|
||||
console.error('Failed to parse JSON',
|
||||
error,
|
||||
title.value.flat(Infinity).join('').replace(/\n/g, '').replace(/\t/g, '').trim()
|
||||
)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
function mapImageUrl (source) {
|
||||
// Implement your mapImageUrl logic here
|
||||
return source
|
||||
}
|
||||
|
||||
return {
|
||||
icon,
|
||||
width,
|
||||
properties,
|
||||
caption,
|
||||
description,
|
||||
src,
|
||||
title,
|
||||
alt,
|
||||
block,
|
||||
value,
|
||||
format,
|
||||
type,
|
||||
visible,
|
||||
hasPageLinkOptions,
|
||||
parent,
|
||||
innerJson
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user