extensions fixed

This commit is contained in:
2025-10-11 20:42:13 +02:00
parent f006927d1a
commit 5d6acfef93
17 changed files with 582 additions and 594 deletions

View File

@ -27,14 +27,34 @@
</div>
</template>
</UButton>
<!-- Console Tab -->
<UButton
:class="['gap-2', showConsole ? 'primary' : 'neutral']"
@click="showConsole = !showConsole"
>
<Icon
name="mdi:console"
size="16"
/>
Console
<UBadge
v-if="visibleLogs.length > 0"
size="xs"
color="primary"
>
{{ visibleLogs.length }}
</UBadge>
</UButton>
</div>
<!-- IFrame Container -->
<div class="flex-1 relative min-h-0">
<!-- Extension IFrames -->
<div
v-for="tab in tabsStore.sortedTabs"
:key="tab.extension.id"
:style="{ display: tab.isVisible ? 'block' : 'none' }"
:style="{ display: tab.isVisible && !showConsole ? 'block' : 'none' }"
class="absolute inset-0"
>
<iframe
@ -48,6 +68,81 @@
/>
</div>
<!-- Console View -->
<div
v-if="showConsole"
class="absolute inset-0 bg-base-100 flex flex-col"
>
<!-- Console Header -->
<div
class="p-2 border-b border-base-300 flex justify-between items-center"
>
<h3 class="font-semibold">Console Output</h3>
<UButton
size="xs"
color="neutral"
variant="ghost"
@click="$clearConsoleLogs()"
>
Clear
</UButton>
</div>
<!-- Console Logs -->
<div class="flex-1 overflow-y-auto p-2 font-mono text-sm">
<!-- Info banner if logs are limited -->
<div
v-if="consoleLogs.length > maxVisibleLogs"
class="mb-2 p-2 bg-warning/10 border border-warning/30 rounded text-xs"
>
Showing last {{ maxVisibleLogs }} of {{ consoleLogs.length }} logs
</div>
<!-- Simple log list instead of accordion for better performance -->
<div
v-if="visibleLogs.length > 0"
class="space-y-1"
>
<div
v-for="(log, index) in visibleLogs"
:key="index"
class="border-b border-base-200 pb-2"
>
<!-- Log header with timestamp and level -->
<div class="flex justify-between items-center mb-1">
<span class="text-xs opacity-60">
[{{ log.timestamp }}] [{{ log.level.toUpperCase() }}]
</span>
<UButton
size="xs"
color="neutral"
variant="ghost"
icon="i-heroicons-clipboard-document"
@click="copyToClipboard(log.message)"
/>
</div>
<!-- Log message -->
<pre
:class="[
'text-xs whitespace-pre-wrap break-all',
log.level === 'error' ? 'text-error' : '',
log.level === 'warn' ? 'text-warning' : '',
log.level === 'info' ? 'text-info' : '',
log.level === 'debug' ? 'text-base-content/70' : '',
]"
>{{ log.message }}</pre>
</div>
</div>
<div
v-if="visibleLogs.length === 0"
class="text-center text-base-content/50 py-8"
>
No console messages yet
</div>
</div>
</div>
<!-- Loading State -->
<div
v-if="tabsStore.tabCount === 0"
@ -68,7 +163,10 @@ import {
import { useExtensionTabsStore } from '~/stores/extensions/tabs'
import type { IHaexHubExtension } from '~/types/haexhub'
import { platform } from '@tauri-apps/plugin-os'
import { EXTENSION_PROTOCOL_NAME, EXTENSION_PROTOCOL_PREFIX } from '~/config/constants'
import {
EXTENSION_PROTOCOL_NAME,
EXTENSION_PROTOCOL_PREFIX,
} from '~/config/constants'
definePageMeta({
name: 'haexExtension',
@ -78,6 +176,23 @@ const { t } = useI18n()
const tabsStore = useExtensionTabsStore()
// Console logging - use global logs from plugin
const { $consoleLogs, $clearConsoleLogs } = useNuxtApp()
const showConsole = ref(false)
const maxVisibleLogs = ref(100) // Limit for performance on mobile
const consoleLogs = $consoleLogs as Ref<
Array<{
timestamp: string
level: 'log' | 'info' | 'warn' | 'error' | 'debug'
message: string
}>
>
// Only show last N logs for performance
const visibleLogs = computed(() => {
return consoleLogs.value.slice(-maxVisibleLogs.value)
})
// Extension aus Route öffnen
//const extensionId = computed(() => route.params.extensionId as string)
@ -94,19 +209,71 @@ const dummyIframeRef = ref<HTMLIFrameElement | null>(null)
const dummyExtensionRef = computed(() => null)
useExtensionMessageHandler(dummyIframeRef, dummyExtensionRef)
// Track which iframes have been registered to prevent duplicate registrations
const registeredIFrames = new WeakSet<HTMLIFrameElement>()
const registerIFrame = (extensionId: string, el: HTMLIFrameElement | null) => {
if (!el) return
// Prevent duplicate registration (Vue calls ref functions on every render)
if (registeredIFrames.has(el)) {
return
}
console.log('[Vue Debug] ========== registerIFrame called ==========')
console.log('[Vue Debug] Extension ID:', extensionId)
console.log('[Vue Debug] Element:', 'HTMLIFrameElement')
// Mark as registered
registeredIFrames.add(el)
// Registriere IFrame im Store
tabsStore.registerIFrame(extensionId, el)
// Registriere IFrame im globalen Message Handler Registry
const tab = tabsStore.openTabs.get(extensionId)
if (tab?.extension) {
console.log('[Vue Debug] Registering iframe in message handler for:', tab.extension.name)
registerExtensionIFrame(el, tab.extension)
console.log('[Vue Debug] Registration complete!')
} else {
console.error('[Vue Debug] ❌ No tab found for extension ID:', extensionId)
}
console.log('[Vue Debug] ========================================')
}
// Listen for console messages from extensions (via postMessage)
const handleExtensionConsole = (event: MessageEvent) => {
if (event.data?.type === 'console.forward') {
const { timestamp, level, message } = event.data.data
consoleLogs.value.push({
timestamp,
level,
message: `[Extension] ${message}`,
})
// Limit to last 1000 logs
if (consoleLogs.value.length > 1000) {
consoleLogs.value = consoleLogs.value.slice(-1000)
}
}
}
onMounted(() => {
window.addEventListener('message', handleExtensionConsole)
})
onBeforeUnmount(() => {
window.removeEventListener('message', handleExtensionConsole)
// Unregister all iframes when the page unmounts
tabsStore.openTabs.forEach((tab) => {
if (tab.iframe) {
unregisterExtensionIFrame(tab.iframe)
}
})
})
// Cleanup wenn Tabs geschlossen werden
watch(
() => tabsStore.openTabs,
@ -184,11 +351,16 @@ watch([currentTheme, locale], () => {
})
})
// Cleanup beim Verlassen
onBeforeUnmount(() => {
// Optional: Alle Tabs schließen oder offen lassen
// tabsStore.closeAllTabs()
})
// Copy to clipboard function
const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text)
// Optional: Show success toast
console.log('Copied to clipboard')
} catch (err) {
console.error('Failed to copy:', err)
}
}
</script>
<i18n lang="yaml">

View File

@ -67,27 +67,15 @@
v-if="filteredExtensions.length"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
>
<template
<!-- Marketplace Extension Card -->
<HaexExtensionMarketplaceCard
v-for="ext in filteredExtensions"
:key="ext.id"
>
<!-- Installed Extension Card -->
<HaexExtensionInstalledCard
v-if="ext.isInstalled"
:extension="ext"
@open="navigateToExtension(ext.id)"
@settings="onShowExtensionSettings(ext)"
@remove="onShowRemoveDialog(ext)"
/>
<!-- Marketplace Extension Card -->
<HaexExtensionMarketplaceCard
v-else
:extension="ext"
:is-installed="isExtensionInstalled(ext.id)"
@install="onInstallFromMarketplace(ext)"
@details="onShowExtensionDetails(ext)"
/>
</template>
:extension="ext"
:is-installed="ext.isInstalled"
@install="onInstallFromMarketplace(ext)"
@details="onShowExtensionDetails(ext)"
/>
</div>
<!-- Empty State -->
@ -132,6 +120,7 @@
import type {
IHaexHubExtension,
IHaexHubExtensionManifest,
IMarketplaceExtension,
} from '~/types/haexhub'
import { open } from '@tauri-apps/plugin-dialog'
import type { ExtensionPreview } from '~~/src-tauri/bindings/ExtensionPreview'
@ -208,7 +197,7 @@ const categories = computed(() => [
])
// Dummy Marketplace Extensions (später von API laden)
const marketplaceExtensions = ref([
const marketplaceExtensions = ref<IMarketplaceExtension[]>([
{
id: 'haex-passy',
name: 'HaexPassDummy',
@ -217,12 +206,14 @@ const marketplaceExtensions = ref([
description:
'Sicherer Passwort-Manager mit Ende-zu-Ende-Verschlüsselung und Autofill-Funktion.',
icon: 'i-heroicons-lock-closed',
homepage: null,
downloads: 15420,
rating: 4.8,
verified: true,
tags: ['security', 'password', 'productivity'],
category: 'security',
downloadUrl: '/extensions/haex-pass-1.0.0.haextension',
isInstalled: false,
},
{
id: 'haex-notes',
@ -232,12 +223,14 @@ const marketplaceExtensions = ref([
description:
'Markdown-basierter Notizen-Editor mit Syntax-Highlighting und Live-Preview.',
icon: 'i-heroicons-document-text',
homepage: null,
downloads: 8930,
rating: 4.5,
verified: true,
tags: ['productivity', 'notes', 'markdown'],
category: 'productivity',
downloadUrl: '/extensions/haex-notes-2.1.0.haextension',
isInstalled: false,
},
{
id: 'haex-backup',
@ -247,12 +240,14 @@ const marketplaceExtensions = ref([
description:
'Automatische Backups deiner Daten mit Cloud-Sync-Unterstützung.',
icon: 'i-heroicons-cloud-arrow-up',
homepage: null,
downloads: 5240,
rating: 4.6,
verified: false,
tags: ['backup', 'cloud', 'utilities'],
category: 'utilities',
downloadUrl: '/extensions/haex-backup-1.5.2.haextension',
isInstalled: false,
},
{
id: 'haex-calendar',
@ -262,12 +257,14 @@ const marketplaceExtensions = ref([
description:
'Integrierter Kalender mit Event-Management und Synchronisation.',
icon: 'i-heroicons-calendar',
homepage: null,
downloads: 12100,
rating: 4.7,
verified: true,
tags: ['productivity', 'calendar', 'events'],
category: 'productivity',
downloadUrl: '/extensions/haex-calendar-3.0.1.haextension',
isInstalled: false,
},
{
id: 'haex-2fa',
@ -277,12 +274,14 @@ const marketplaceExtensions = ref([
description:
'2-Faktor-Authentifizierung Manager mit TOTP und Backup-Codes.',
icon: 'i-heroicons-shield-check',
homepage: null,
downloads: 7800,
rating: 4.9,
verified: true,
tags: ['security', '2fa', 'authentication'],
category: 'security',
downloadUrl: '/extensions/haex-2fa-1.2.0.haextension',
isInstalled: false,
},
{
id: 'haex-github',
@ -292,39 +291,26 @@ const marketplaceExtensions = ref([
description:
'Direkter Zugriff auf GitHub Repositories, Issues und Pull Requests.',
icon: 'i-heroicons-code-bracket',
homepage: null,
downloads: 4120,
rating: 4.3,
verified: false,
tags: ['integration', 'github', 'development'],
category: 'integration',
downloadUrl: '/extensions/haex-github-1.0.5.haextension',
isInstalled: false,
},
])
// Combine installed extensions with marketplace extensions
const allExtensions = computed(() => {
// Map installed extensions to marketplace format
const installed = extensionStore.availableExtensions.map((ext) => ({
id: ext.id,
name: ext.name,
version: ext.version,
author: ext.author || 'Unknown',
description: 'Installed Extension',
icon: ext.icon || 'i-heroicons-puzzle-piece',
downloads: 0,
rating: 0,
verified: false,
tags: [],
category: 'utilities',
downloadUrl: '',
isInstalled: true,
// Mark marketplace extensions as installed if they exist in availableExtensions
const allExtensions = computed((): IMarketplaceExtension[] => {
return marketplaceExtensions.value.map((ext) => ({
...ext,
// Check if this marketplace extension is already installed
isInstalled: extensionStore.availableExtensions.some(
(installed) => installed.name === ext.name,
),
}))
console.log('Installed extensions count:', installed.length)
console.log('All extensions:', [...installed, ...marketplaceExtensions.value])
// Merge with marketplace extensions
return [...installed, ...marketplaceExtensions.value]
})
// Filtered Extensions
@ -333,7 +319,7 @@ const filteredExtensions = computed(() => {
const matchesSearch =
!searchQuery.value ||
ext.name.toLowerCase().includes(searchQuery.value.toLowerCase()) ||
ext.description.toLowerCase().includes(searchQuery.value.toLowerCase())
ext.description?.toLowerCase().includes(searchQuery.value.toLowerCase())
const matchesCategory =
selectedCategory.value === 'all' ||
@ -343,14 +329,6 @@ const filteredExtensions = computed(() => {
})
})
// Check if extension is installed
const isExtensionInstalled = (extensionId: string) => {
return (
extensionStore.availableExtensions.some((ext) => ext.id === extensionId) ||
allExtensions.value.some((ext) => ext.id === extensionId)
)
}
// Install from marketplace
const onInstallFromMarketplace = async (ext: unknown) => {
console.log('Install from marketplace:', ext)
@ -364,35 +342,6 @@ const onShowExtensionDetails = (ext: unknown) => {
// TODO: Show extension details modal
}
// Navigate to installed extension
const router = useRouter()
const route = useRoute()
const localePath = useLocalePath()
const navigateToExtension = (extensionId: string) => {
router.push(
localePath({
name: 'haexExtension',
params: {
vaultId: route.params.vaultId,
extensionId,
},
}),
)
}
// Show extension settings
const onShowExtensionSettings = (ext: unknown) => {
console.log('Show settings:', ext)
// TODO: Show extension settings modal
}
// Show remove dialog
const onShowRemoveDialog = (ext: any) => {
extensionToBeRemoved.value = ext
showRemoveDialog.value = true
}
const onSelectExtensionAsync = async () => {
try {
extension.path = await open({ directory: false, recursive: true })
@ -405,7 +354,7 @@ const onSelectExtensionAsync = async () => {
// Check if already installed using full_extension_id
const fullExtensionId = `${preview.value.key_hash}_${preview.value.manifest.name}_${preview.value.manifest.version}`
const isAlreadyInstalled = extensionStore.availableExtensions.some(
ext => ext.id === fullExtensionId
(ext) => ext.id === fullExtensionId,
)
if (isAlreadyInstalled) {