feat: send active unraid theme to docs (#1400)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- The changelog modal now automatically syncs its theme (dark or light
mode) with the rest of the application.
- Added a new test button to preview the changelog from a localhost URL.
- **Improvements**
- Enhanced navigation handling within the embedded changelog, ensuring
only allowed links are followed.
- Improved communication between the modal and its embedded content for
a more seamless user experience.
- Updated the app layout to better support light and dark themes with
consistent background and text colors.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Eli Bosley
2025-05-21 13:35:30 -07:00
committed by GitHub
parent 9ce2fee380
commit f71943b62b
3 changed files with 138 additions and 58 deletions

View File

@@ -10,13 +10,14 @@ import {
ServerStackIcon,
} from '@heroicons/vue/24/solid';
import { BrandButton, BrandLoading } from '@unraid/ui';
import { allowedDocsOriginRegex, allowedDocsUrlRegex } from '~/helpers/urls';
import type { ComposerTranslation } from 'vue-i18n';
import RawChangelogRenderer from '~/components/UpdateOs/RawChangelogRenderer.vue';
import { usePurchaseStore } from '~/store/purchase';
import { useThemeStore } from '~/store/theme';
import { useUpdateOsStore } from '~/store/updateOs';
import { allowedDocsOriginRegex, allowedDocsUrlRegex } from '~/helpers/urls';
export interface Props {
open?: boolean;
@@ -29,6 +30,8 @@ const props = withDefaults(defineProps<Props>(), {
const purchaseStore = usePurchaseStore();
const updateOsStore = useUpdateOsStore();
const themeStore = useThemeStore();
const { darkMode } = storeToRefs(themeStore);
const { availableWithRenewal, releaseForUpdate, changelogModalVisible } = storeToRefs(updateOsStore);
const { setReleaseForUpdate, fetchAndConfirmInstall } = updateOsStore;
@@ -40,6 +43,7 @@ const showExtendKeyButton = computed(() => {
const iframeRef = ref<HTMLIFrameElement | null>(null);
const hasNavigated = ref(false);
const currentIframeUrl = ref<string | null>(null);
const actualIframeSrc = ref<string | null>(null);
const docsChangelogUrl = computed(() => {
return releaseForUpdate.value?.changelogPretty ?? null;
@@ -49,36 +53,50 @@ const showRawChangelog = computed<boolean>(() => {
return !docsChangelogUrl.value && !!releaseForUpdate.value?.changelog;
});
const handleIframeNavigationMessage = (event: MessageEvent) => {
const handleDocsPostMessages = (event: MessageEvent) => {
// Common checks for all iframe messages
if (
event.data &&
event.data.type === 'unraid-docs-navigation' &&
iframeRef.value &&
event.source === iframeRef.value.contentWindow &&
allowedDocsOriginRegex.test(event.origin)
(allowedDocsOriginRegex.test(event.origin) || event.origin === 'http://localhost:3000')
) {
if (
typeof event.data.url === 'string' &&
allowedDocsUrlRegex.test(event.data.url)
) {
if (event.data.url !== docsChangelogUrl.value) {
hasNavigated.value = true;
} else {
hasNavigated.value = false;
// Handle navigation events
if (event.data.type === 'unraid-docs-navigation') {
if (typeof event.data.url === 'string' && allowedDocsUrlRegex.test(event.data.url)) {
hasNavigated.value = event.data.url !== docsChangelogUrl.value;
currentIframeUrl.value = event.data.url;
}
currentIframeUrl.value = event.data.url;
}
// Handle theme ready events
else if (event.data.type === 'theme-ready') {
sendThemeToIframe();
}
}
};
// Keep this function just for the watch handler
const sendThemeToIframe = () => {
if (iframeRef.value && iframeRef.value.contentWindow) {
try {
const message = { type: 'theme-update', theme: darkMode.value ? 'dark' : 'light' };
iframeRef.value.contentWindow.postMessage(message, '*');
} catch (error) {
console.error('Failed to send theme to iframe:', error);
}
}
};
// Attach event listener right away instead of waiting for mount
onMounted(() => {
window.addEventListener('message', handleIframeNavigationMessage);
// Set initial value
// Set initial values only
window.addEventListener('message', handleDocsPostMessages);
currentIframeUrl.value = docsChangelogUrl.value;
});
onBeforeUnmount(() => {
window.removeEventListener('message', handleIframeNavigationMessage);
window.removeEventListener('message', handleDocsPostMessages);
});
const revertToInitialChangelog = () => {
@@ -92,7 +110,20 @@ const revertToInitialChangelog = () => {
watch(docsChangelogUrl, (newUrl) => {
currentIframeUrl.value = newUrl;
hasNavigated.value = false;
if (newUrl) {
actualIframeSrc.value = newUrl;
} else {
actualIframeSrc.value = null;
}
});
// Only need to watch for theme changes
watch(darkMode, () => {
// The iframe will only pick up the message if it has sent theme-ready
sendThemeToIframe();
});
</script>
<template>
@@ -113,10 +144,11 @@ watch(docsChangelogUrl, (newUrl) => {
<!-- iframe for changelog if available -->
<div v-if="docsChangelogUrl" class="w-[calc(100%+3rem)] h-[475px] -mx-6 -my-6">
<iframe
v-if="actualIframeSrc"
ref="iframeRef"
:src="docsChangelogUrl"
:src="actualIframeSrc"
class="w-full h-full border-0 rounded-md"
sandbox="allow-scripts allow-same-origin"
sandbox="allow-scripts allow-same-origin allow-top-navigation"
title="Unraid Changelog"
></iframe>
</div>

View File

@@ -1,25 +1,41 @@
<template>
<client-only>
<div class="flex flex-row items-center justify-center gap-6 p-6 text-gray-200 bg-zinc-800">
<template v-for="route in routes" :key="route.path">
<NuxtLink
:to="route.path"
class="underline hover:no-underline focus:no-underline"
active-class="text-orange"
>
{{ formatRouteName(route.name) }}
</NuxtLink>
</template>
<ModalsCe />
</div>
<slot />
</client-only>
<div class="text-black bg-white dark:text-white dark:bg-black">
<client-only>
<div class="flex flex-row items-center justify-center gap-6 p-6 bg-white dark:bg-zinc-800">
<template v-for="route in routes" :key="route.path">
<NuxtLink
:to="route.path"
class="underline hover:no-underline focus:no-underline"
active-class="text-orange"
>
{{ formatRouteName(route.name) }}
</NuxtLink>
</template>
<ModalsCe />
</div>
<slot />
</client-only>
</div>
</template>
<script setup>
import ModalsCe from '~/components/Modals.ce.vue';
import { useThemeStore } from '~/store/theme';
const router = useRouter();
const themeStore = useThemeStore();
const { theme } = storeToRefs(themeStore);
// Watch for theme changes (satisfies linter by using theme)
watch(
theme,
() => {
// Theme is being watched for reactivity
console.debug('Theme changed:', theme.value);
},
{ immediate: true }
);
const routes = computed(() => {
return router
.getRoutes()
@@ -37,3 +53,9 @@ function formatRouteName(name) {
.join(' ');
}
</script>
<style lang="postcss">
/* Import theme styles */
@import '@unraid/ui/styles';
@import '~/assets/main.css';
</style>

View File

@@ -7,6 +7,9 @@ const updateOsStore = useUpdateOsStore();
const { changelogModalVisible } = storeToRefs(updateOsStore);
const { t } = useI18n();
onBeforeMount(() => {
// Register custom elements if needed for ColorSwitcherCe
});
async function showChangelogModalFromReleasesEndpoint() {
const response = await fetch('https://releases.unraid.net/os?branch=stable&current_version=6.12.3');
@@ -26,6 +29,7 @@ function showChangelogModalWithTestData() {
sha256: '1234567890'
});
}
function showChangelogWithoutPretty() {
updateOsStore.setReleaseForUpdate({
version: '6.12.3',
@@ -52,37 +56,59 @@ function showChangelogBrokenParse() {
});
}
function showChangelogFromLocalhost() {
updateOsStore.setReleaseForUpdate({
version: '6.12.3',
date: '2023-07-15',
changelog: 'https://raw.githubusercontent.com/unraid/docs/main/docs/unraid-os/release-notes/6.12.3.md',
changelogPretty: 'http://localhost:3000/unraid-os/release-notes/6.12.3',
name: '6.12.3',
isEligible: true,
isNewer: true,
sha256: '1234567890'
});
}
</script>
<template>
<div class="container mx-auto p-6">
<h1 class="text-2xl font-bold mb-6">Changelog</h1>
<UpdateOsChangelogModal :t="t" :open="changelogModalVisible" />
<div class="mb-6 flex flex-col gap-4 max-w-md">
<button
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
@click="showChangelogModalFromReleasesEndpoint"
>
Test Changelog Modal (from releases endpoint)
</button>
<button
class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
@click="showChangelogModalWithTestData"
>
Test Changelog Modal (with test data)
</button>
<button
class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
@click="showChangelogWithoutPretty"
>
Test Without Pretty Changelog
</button>
<button
class="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
@click="showChangelogBrokenParse"
>
Test Broken Parse Changelog
</button>
<div class="mb-6 flex flex-col gap-4">
<ColorSwitcherCe />
<div class="max-w-md flex flex-col gap-4">
<button
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
@click="showChangelogModalFromReleasesEndpoint"
>
Test Changelog Modal (from releases endpoint)
</button>
<button
class="px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
@click="showChangelogFromLocalhost"
>
Test Local Pretty Changelog (:3000)
</button>
<button
class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
@click="showChangelogModalWithTestData"
>
Test Changelog Modal (with test data)
</button>
<button
class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
@click="showChangelogWithoutPretty"
>
Test Without Pretty Changelog
</button>
<button
class="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
@click="showChangelogBrokenParse"
>
Test Broken Parse Changelog
</button>
</div>
</div>
</div>
</template>