mirror of
https://github.com/unraid/api.git
synced 2025-12-31 13:39:52 -06:00
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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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¤t_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>
|
||||
|
||||
Reference in New Issue
Block a user