Skip to content

Commit

Permalink
feat(katzencore): Using Storage API from unstorage, plugin now works …
Browse files Browse the repository at this point in the history
…with implementing JWT, Production Test, Image Selector built
  • Loading branch information
maxlkatze committed Jul 19, 2024
1 parent 192120e commit a89a76f
Show file tree
Hide file tree
Showing 15 changed files with 522 additions and 289 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
},
"dependencies": {
"@nuxt/kit": "3.12.3",
"@nuxt/kit": "3.12.4",
"@nuxtjs/tailwindcss": "6.12.1",
"@pinia/nuxt": "^0.5.1",
"@tiptap/extension-highlight": "^2.5.2",
Expand All @@ -45,14 +45,14 @@
"@nuxt/eslint-config": "0.3.13",
"@nuxt/image": "1.7.0",
"@nuxt/module-builder": "0.8.1",
"@nuxt/schema": "3.12.3",
"@nuxt/schema": "3.12.4",
"@nuxt/test-utils": "3.13.1",
"@types/jsonwebtoken": "9.0.6",
"@types/node": "20.14.10",
"changelogen": "0.5.5",
"eslint": "9.7.0",
"jsonwebtoken": "9.0.2",
"nuxt": "3.12.3",
"nuxt": "3.12.4",
"typescript": "5.5.3",
"vitest": "2.0.3",
"vue-tsc": "2.0.26"
Expand Down
Binary file added playground/public/images/test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
545 changes: 328 additions & 217 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

30 changes: 22 additions & 8 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import {
extendPages,
installModule,
} from '@nuxt/kit'
import katze_content_path from './path'
import {createStorage} from 'unstorage';
import fsDriver from 'unstorage/drivers/fs';

/*
THIS MODULE IS THE CORE OF THE KATZENFRAMEWORK
Expand Down Expand Up @@ -52,7 +53,13 @@ export default defineNuxtModule<ModuleOptions>({
console.info('[KATZE] Module installed; Running Setup')

await installModules()
addPlugin(resolve('./runtime/plugin'))
await addImports()

addPlugin(resolve('./runtime/plugins/chtml.plugin'))
addPlugin({
src: resolve('./runtime/plugins/plugin.server'),
mode: 'server',
})

// ADD BACKEND CMS PAGE
extendPages(
Expand All @@ -75,10 +82,12 @@ export default defineNuxtModule<ModuleOptions>({
_nuxt.options.runtimeConfig.users = _options.users
_nuxt.options.runtimeConfig.secret = _options.secret
_nuxt.options.runtimeConfig.projectLocation = _options.projectLocation + (_options.projectLocation.endsWith('/') ? '' : '/')
const content_path = _nuxt.options.runtimeConfig.projectLocation + katze_content_path
const content = fs.existsSync(content_path) ? JSON.parse(fs.readFileSync(content_path, 'utf8')) : {}
const storage = createStorage({
driver: fsDriver({ base: _nuxt.options.runtimeConfig.projectLocation + '/' + 'public/' }),
})
const content = await storage.hasItem('content.katze.json') ? await storage.getItem('content.katze.json') as object : {}
_nuxt.options.runtimeConfig.public.content = content
console.info('[KATZE] Content loaded from ' + content_path + ' with ' + Object.entries(content).length + ' entries')
console.info('[KATZE] Content loaded from storage with ' + Object.entries(content).length + ' entries')

addRouteMiddleware({
name: 'auth',
Expand Down Expand Up @@ -111,11 +120,17 @@ export default defineNuxtModule<ModuleOptions>({
await addComponentsDir({
path: resolve('runtime/components/ui'),
})

addImportsDir(resolve('runtime/composables'))
},
})

const addImports = async () => {
const { resolve } = createResolver(import.meta.url)
addImportsDir(resolve('runtime/composables'))
addImportsDir(resolve('runtime/components'))
addImportsDir(resolve('runtime/stores'))
addImportsDir(resolve('runtime/middleware'))
}

const installModules = async () => {
const { resolve } = createResolver(import.meta.url)
await installModule('@nuxtjs/tailwindcss', {
Expand All @@ -134,7 +149,6 @@ const installModules = async () => {
})

await installModule('@nuxt/image')
console.log('installing pinia')
await installModule('@pinia/nuxt', {
storesDirs: [
'./runtime/stores/**',
Expand Down
1 change: 0 additions & 1 deletion src/path.ts

This file was deleted.

115 changes: 109 additions & 6 deletions src/runtime/components/views/EditView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import Highlight from '@tiptap/extension-highlight'
import Placeholder from '@tiptap/extension-placeholder'
import { Extension } from '@tiptap/core'
import { ComponentType, type KatzenUIComponent, useUiStore } from '../../stores/UiStore'
import type { ImageContent } from '../../composables/useUiComponents'
import { computed, defineAsyncComponent, onMounted, ref, shallowRef, useCookie, watch } from '#imports'
import { useRouter } from '#app'
import { useAsyncData, useRouter } from '#app'
const DisableEnter = Extension.create({
addKeyboardShortcuts() {
Expand Down Expand Up @@ -58,6 +59,8 @@ const selectedElement = ref<HTMLElement | null>(null)
const currentSelectedKey = ref<string | undefined>(undefined)
const currentSelectedComponent = ref<KatzenUIComponent | undefined>(undefined)
const selectedImage = ref<string | undefined>(undefined)
watch(selectedRoute, async () => {
if (selectedRoute.value.path) {
Route.value = await defineAsyncComponent(() => import(`~/pages/${selectedRoute.value.name}.vue`))
Expand Down Expand Up @@ -97,7 +100,15 @@ onMounted(
if (component) {
currentSelectedKey.value = attribute
currentSelectedComponent.value = component
editor.value?.commands.setContent(component.content as string)
if (component.type === ComponentType.Image) {
const content = component.content as ImageContent || { src: '', alt: '' }
editor.value?.commands.setContent(content.alt)
selectedImage.value = content.src
}
else {
editor.value?.commands.setContent(component.content as string)
}
}
}
event.stopPropagation()
Expand Down Expand Up @@ -159,6 +170,7 @@ const editor = useEditor({
if (currentSelectedKey.value) {
const html = editor.getHTML()
const dom = new DOMParser().parseFromString(html, 'text/html')
const text = dom.body.textContent || ''
if (currentSelectedComponent.value?.type === ComponentType.RichText) {
const newDom = document.createElement('body')
Expand All @@ -174,10 +186,16 @@ const editor = useEditor({
})
uiStore.updateUiContent(currentSelectedKey.value, newDom.innerHTML)
}
else {
const text = dom.body.textContent || ''
else if (currentSelectedComponent.value?.type === ComponentType.Text) {
uiStore.updateUiContent(currentSelectedKey.value, text)
}
else if (currentSelectedComponent.value?.type === ComponentType.Image) {
const content = currentSelectedComponent.value.content as ImageContent || { src: '', alt: '' }
uiStore.updateUiContent(currentSelectedKey.value, {
src: content.src,
alt: text,
})
}
}
},
Expand Down Expand Up @@ -239,6 +257,50 @@ const saveUiContent = async () => {
saveSuccess.value = false
}, 2000)
}
interface ImageListResponse {
body: {
images: string[]
}
}
const { data: imageResponse } = useAsyncData(async () => await $fetch<ImageListResponse>('/content-cms', {
method: 'POST',
body: {
token: useCookie('token').value,
action: 'imageList',
},
}), {
default: (): ImageListResponse => ({
body: {
images: [],
},
}),
})
// if the images are not 9 in total, add empty strings to the array
const fillEmptySpace = computed(() => {
const rest = 9 - imageResponse.value.body.images.length
if (rest > 0) {
return Array.from({ length: rest }).fill('')
}
// if not 3 last images, fill with empty strings
const mod = imageResponse.value.body.images.length % 3
if (mod > 0) {
return Array.from({ length: 3 - mod }).fill('')
}
})
watch(selectedImage, () => {
if (selectedImage.value && currentSelectedKey.value) {
const content = currentSelectedComponent.value?.content as ImageContent || { src: '', alt: '' }
uiStore.updateUiContent(currentSelectedKey.value, {
src: selectedImage.value,
alt: content.alt,
})
}
})
</script>

<template>
Expand Down Expand Up @@ -323,7 +385,7 @@ const saveUiContent = async () => {
src="../../assets/icons/close.svg"
class="size-6 ml-auto cursor-pointer"
alt="close"
@click="selectedElement=null"
@click="selectedElement=null; currentSelectedKey=undefined"
>
</div>

Expand All @@ -343,6 +405,47 @@ const saveUiContent = async () => {
</button>
</div>
</div>
<!-- IMAGE CHOOSER / UPLOAD -->

<template v-if="currentSelectedComponent?.type === ComponentType.Image">
<div class="flex flex-col items-center">
<button
class="bg-black text-white px-5 py-3 rounded-2xl transition-all flex
justify-center content-center items-center justify-items-center border-2 border-black"
>
Upload Image
</button>
<span>OR</span>
<div class="h-72 w-72 grid grid-cols-3 grid-flow-row overflow-y-auto justify-center items-center">
<div
v-for="(image, index) in imageResponse.body.images"
:key="index"
class="w-full aspect-square"
>
<img
:src="image"
class="object-cover size-full hover:drop-shadow cursor-pointer border-2 border-transparent
contain-inline-size hover:border-purple-400 transition-colors duration-300 rounded"
alt="image"
:class="{ 'border-yellow-500': selectedImage === image, 'animate-pulse': selectedImage === image }"
@click="selectedImage = image"
>
</div>
<div
v-for="(image, index) in fillEmptySpace"
:key="index"
class="w-full contain-inline-size cursor-not-allowed rounded border-2 border-transparent aspect-square"
>
<div class="bg-gray-100 size-full" />
</div>
</div>
</div>
<p class="font-mono font-bold">
ALT:
</p>
</template>

<!-- TEXT INPUT -->
<EditorContent
class="size-full max-h-72 overflow-y-auto border-b-2 border-black"
:editor="editor"
Expand All @@ -363,7 +466,7 @@ const saveUiContent = async () => {
:class="{ invert: !saveSuccess && !saveLoading }"
alt="loading"
>
Save
Save Draft
<span
class="transition-all duration-300 w-0 ml-0"
:class="{ '!w-4': saveLoading || saveSuccess, '!ml-2': saveLoading || saveSuccess }"
Expand Down
5 changes: 1 addition & 4 deletions src/runtime/composables/useUiComponents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface FetchedContentType {
content: Record<string, unknown>
}

interface ImageContent {
export interface ImageContent {
src: string
alt: string
}
Expand Down Expand Up @@ -80,8 +80,5 @@ const reactiveProperty = <T = string>(key: string) => {
watch(uiComponent, (newValue) => {
contentRef.value = newValue?.content || ''
}, { deep: true })

console.log('contentRef', contentRef)

return contentRef as T
}
8 changes: 4 additions & 4 deletions src/runtime/middleware/authentication.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { defineNuxtRouteMiddleware, useCookie, useRuntimeConfig } from '#imports'
import {defineNuxtRouteMiddleware, useCookie, useNuxtApp, useRuntimeConfig} from '#imports'

export default defineNuxtRouteMiddleware(async (to) => {
const checkAuth = import.meta.client ? clientSideAuthentication : serverSideAuthentication
Expand All @@ -24,9 +24,9 @@ export default defineNuxtRouteMiddleware(async (to) => {
const serverSideAuthentication = async () => {
const token = useCookie('token')
if (!token.value) return false
// eslint-disable-next-line @typescript-eslint/no-var-requires
const jwt = require('jsonwebtoken')
return jwt.verify(token.value, useRuntimeConfig().secret)
const runtimeConfig = useRuntimeConfig()
const {$verifyJwtToken} = useNuxtApp()
return $verifyJwtToken(token.value, runtimeConfig.secret||'')
}

const clientSideAuthentication = async () => {
Expand Down
3 changes: 3 additions & 0 deletions src/runtime/plugin.ts → src/runtime/plugins/chtml.plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,7 @@ export default defineNuxtPlugin((_nuxtApp) => {
}
},
})



})
12 changes: 12 additions & 0 deletions src/runtime/plugins/plugin.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import jwt from 'jsonwebtoken'
import { defineNuxtPlugin } from '#imports'

export default defineNuxtPlugin((_nuxtApp) => {
return {
provide: {
verifyJwtToken: (token: string, secret: string, options: object) => {
return jwt.verify(token, secret, options)
},
},
}
})
Loading

0 comments on commit a89a76f

Please sign in to comment.