mirror of
https://github.com/unraid/api.git
synced 2026-01-02 06:30:02 -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,
|
ServerStackIcon,
|
||||||
} from '@heroicons/vue/24/solid';
|
} from '@heroicons/vue/24/solid';
|
||||||
import { BrandButton, BrandLoading } from '@unraid/ui';
|
import { BrandButton, BrandLoading } from '@unraid/ui';
|
||||||
|
import { allowedDocsOriginRegex, allowedDocsUrlRegex } from '~/helpers/urls';
|
||||||
|
|
||||||
import type { ComposerTranslation } from 'vue-i18n';
|
import type { ComposerTranslation } from 'vue-i18n';
|
||||||
|
|
||||||
import RawChangelogRenderer from '~/components/UpdateOs/RawChangelogRenderer.vue';
|
import RawChangelogRenderer from '~/components/UpdateOs/RawChangelogRenderer.vue';
|
||||||
import { usePurchaseStore } from '~/store/purchase';
|
import { usePurchaseStore } from '~/store/purchase';
|
||||||
|
import { useThemeStore } from '~/store/theme';
|
||||||
import { useUpdateOsStore } from '~/store/updateOs';
|
import { useUpdateOsStore } from '~/store/updateOs';
|
||||||
import { allowedDocsOriginRegex, allowedDocsUrlRegex } from '~/helpers/urls';
|
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
@@ -29,6 +30,8 @@ const props = withDefaults(defineProps<Props>(), {
|
|||||||
|
|
||||||
const purchaseStore = usePurchaseStore();
|
const purchaseStore = usePurchaseStore();
|
||||||
const updateOsStore = useUpdateOsStore();
|
const updateOsStore = useUpdateOsStore();
|
||||||
|
const themeStore = useThemeStore();
|
||||||
|
const { darkMode } = storeToRefs(themeStore);
|
||||||
const { availableWithRenewal, releaseForUpdate, changelogModalVisible } = storeToRefs(updateOsStore);
|
const { availableWithRenewal, releaseForUpdate, changelogModalVisible } = storeToRefs(updateOsStore);
|
||||||
const { setReleaseForUpdate, fetchAndConfirmInstall } = updateOsStore;
|
const { setReleaseForUpdate, fetchAndConfirmInstall } = updateOsStore;
|
||||||
|
|
||||||
@@ -40,6 +43,7 @@ const showExtendKeyButton = computed(() => {
|
|||||||
const iframeRef = ref<HTMLIFrameElement | null>(null);
|
const iframeRef = ref<HTMLIFrameElement | null>(null);
|
||||||
const hasNavigated = ref(false);
|
const hasNavigated = ref(false);
|
||||||
const currentIframeUrl = ref<string | null>(null);
|
const currentIframeUrl = ref<string | null>(null);
|
||||||
|
const actualIframeSrc = ref<string | null>(null);
|
||||||
|
|
||||||
const docsChangelogUrl = computed(() => {
|
const docsChangelogUrl = computed(() => {
|
||||||
return releaseForUpdate.value?.changelogPretty ?? null;
|
return releaseForUpdate.value?.changelogPretty ?? null;
|
||||||
@@ -49,36 +53,50 @@ const showRawChangelog = computed<boolean>(() => {
|
|||||||
return !docsChangelogUrl.value && !!releaseForUpdate.value?.changelog;
|
return !docsChangelogUrl.value && !!releaseForUpdate.value?.changelog;
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleIframeNavigationMessage = (event: MessageEvent) => {
|
const handleDocsPostMessages = (event: MessageEvent) => {
|
||||||
|
// Common checks for all iframe messages
|
||||||
if (
|
if (
|
||||||
event.data &&
|
event.data &&
|
||||||
event.data.type === 'unraid-docs-navigation' &&
|
|
||||||
iframeRef.value &&
|
iframeRef.value &&
|
||||||
event.source === iframeRef.value.contentWindow &&
|
event.source === iframeRef.value.contentWindow &&
|
||||||
allowedDocsOriginRegex.test(event.origin)
|
(allowedDocsOriginRegex.test(event.origin) || event.origin === 'http://localhost:3000')
|
||||||
) {
|
) {
|
||||||
if (
|
// Handle navigation events
|
||||||
typeof event.data.url === 'string' &&
|
if (event.data.type === 'unraid-docs-navigation') {
|
||||||
allowedDocsUrlRegex.test(event.data.url)
|
if (typeof event.data.url === 'string' && allowedDocsUrlRegex.test(event.data.url)) {
|
||||||
) {
|
hasNavigated.value = event.data.url !== docsChangelogUrl.value;
|
||||||
if (event.data.url !== docsChangelogUrl.value) {
|
currentIframeUrl.value = event.data.url;
|
||||||
hasNavigated.value = true;
|
|
||||||
} else {
|
|
||||||
hasNavigated.value = false;
|
|
||||||
}
|
}
|
||||||
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(() => {
|
onMounted(() => {
|
||||||
window.addEventListener('message', handleIframeNavigationMessage);
|
// Set initial values only
|
||||||
// Set initial value
|
window.addEventListener('message', handleDocsPostMessages);
|
||||||
currentIframeUrl.value = docsChangelogUrl.value;
|
currentIframeUrl.value = docsChangelogUrl.value;
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('message', handleIframeNavigationMessage);
|
window.removeEventListener('message', handleDocsPostMessages);
|
||||||
});
|
});
|
||||||
|
|
||||||
const revertToInitialChangelog = () => {
|
const revertToInitialChangelog = () => {
|
||||||
@@ -92,7 +110,20 @@ const revertToInitialChangelog = () => {
|
|||||||
watch(docsChangelogUrl, (newUrl) => {
|
watch(docsChangelogUrl, (newUrl) => {
|
||||||
currentIframeUrl.value = newUrl;
|
currentIframeUrl.value = newUrl;
|
||||||
hasNavigated.value = false;
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -113,10 +144,11 @@ watch(docsChangelogUrl, (newUrl) => {
|
|||||||
<!-- iframe for changelog if available -->
|
<!-- iframe for changelog if available -->
|
||||||
<div v-if="docsChangelogUrl" class="w-[calc(100%+3rem)] h-[475px] -mx-6 -my-6">
|
<div v-if="docsChangelogUrl" class="w-[calc(100%+3rem)] h-[475px] -mx-6 -my-6">
|
||||||
<iframe
|
<iframe
|
||||||
|
v-if="actualIframeSrc"
|
||||||
ref="iframeRef"
|
ref="iframeRef"
|
||||||
:src="docsChangelogUrl"
|
:src="actualIframeSrc"
|
||||||
class="w-full h-full border-0 rounded-md"
|
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"
|
title="Unraid Changelog"
|
||||||
></iframe>
|
></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,25 +1,41 @@
|
|||||||
<template>
|
<template>
|
||||||
<client-only>
|
<div class="text-black bg-white dark:text-white dark:bg-black">
|
||||||
<div class="flex flex-row items-center justify-center gap-6 p-6 text-gray-200 bg-zinc-800">
|
<client-only>
|
||||||
<template v-for="route in routes" :key="route.path">
|
<div class="flex flex-row items-center justify-center gap-6 p-6 bg-white dark:bg-zinc-800">
|
||||||
<NuxtLink
|
<template v-for="route in routes" :key="route.path">
|
||||||
:to="route.path"
|
<NuxtLink
|
||||||
class="underline hover:no-underline focus:no-underline"
|
:to="route.path"
|
||||||
active-class="text-orange"
|
class="underline hover:no-underline focus:no-underline"
|
||||||
>
|
active-class="text-orange"
|
||||||
{{ formatRouteName(route.name) }}
|
>
|
||||||
</NuxtLink>
|
{{ formatRouteName(route.name) }}
|
||||||
</template>
|
</NuxtLink>
|
||||||
<ModalsCe />
|
</template>
|
||||||
</div>
|
<ModalsCe />
|
||||||
<slot />
|
</div>
|
||||||
</client-only>
|
<slot />
|
||||||
|
</client-only>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ModalsCe from '~/components/Modals.ce.vue';
|
import ModalsCe from '~/components/Modals.ce.vue';
|
||||||
|
import { useThemeStore } from '~/store/theme';
|
||||||
|
|
||||||
const router = useRouter();
|
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(() => {
|
const routes = computed(() => {
|
||||||
return router
|
return router
|
||||||
.getRoutes()
|
.getRoutes()
|
||||||
@@ -37,3 +53,9 @@ function formatRouteName(name) {
|
|||||||
.join(' ');
|
.join(' ');
|
||||||
}
|
}
|
||||||
</script>
|
</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 { changelogModalVisible } = storeToRefs(updateOsStore);
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
// Register custom elements if needed for ColorSwitcherCe
|
||||||
|
});
|
||||||
|
|
||||||
async function showChangelogModalFromReleasesEndpoint() {
|
async function showChangelogModalFromReleasesEndpoint() {
|
||||||
const response = await fetch('https://releases.unraid.net/os?branch=stable¤t_version=6.12.3');
|
const response = await fetch('https://releases.unraid.net/os?branch=stable¤t_version=6.12.3');
|
||||||
@@ -26,6 +29,7 @@ function showChangelogModalWithTestData() {
|
|||||||
sha256: '1234567890'
|
sha256: '1234567890'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function showChangelogWithoutPretty() {
|
function showChangelogWithoutPretty() {
|
||||||
updateOsStore.setReleaseForUpdate({
|
updateOsStore.setReleaseForUpdate({
|
||||||
version: '6.12.3',
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container mx-auto p-6">
|
<div class="container mx-auto p-6">
|
||||||
<h1 class="text-2xl font-bold mb-6">Changelog</h1>
|
<h1 class="text-2xl font-bold mb-6">Changelog</h1>
|
||||||
<UpdateOsChangelogModal :t="t" :open="changelogModalVisible" />
|
<UpdateOsChangelogModal :t="t" :open="changelogModalVisible" />
|
||||||
<div class="mb-6 flex flex-col gap-4 max-w-md">
|
<div class="mb-6 flex flex-col gap-4">
|
||||||
<button
|
<ColorSwitcherCe />
|
||||||
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
<div class="max-w-md flex flex-col gap-4">
|
||||||
@click="showChangelogModalFromReleasesEndpoint"
|
<button
|
||||||
>
|
class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
|
||||||
Test Changelog Modal (from releases endpoint)
|
@click="showChangelogModalFromReleasesEndpoint"
|
||||||
</button>
|
>
|
||||||
<button
|
Test Changelog Modal (from releases endpoint)
|
||||||
class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
|
</button>
|
||||||
@click="showChangelogModalWithTestData"
|
<button
|
||||||
>
|
class="px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
|
||||||
Test Changelog Modal (with test data)
|
@click="showChangelogFromLocalhost"
|
||||||
</button>
|
>
|
||||||
<button
|
Test Local Pretty Changelog (:3000)
|
||||||
class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
|
</button>
|
||||||
@click="showChangelogWithoutPretty"
|
<button
|
||||||
>
|
class="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
|
||||||
Test Without Pretty Changelog
|
@click="showChangelogModalWithTestData"
|
||||||
</button>
|
>
|
||||||
<button
|
Test Changelog Modal (with test data)
|
||||||
class="px-4 py-2 bg-yellow-500 text-white rounded hover:bg-yellow-600"
|
</button>
|
||||||
@click="showChangelogBrokenParse"
|
<button
|
||||||
>
|
class="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600"
|
||||||
Test Broken Parse Changelog
|
@click="showChangelogWithoutPretty"
|
||||||
</button>
|
>
|
||||||
|
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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user