some UI improvements
This commit is contained in:
parent
ce7f9ac236
commit
d544413cd0
3 changed files with 58 additions and 10 deletions
|
|
@ -26,10 +26,18 @@ const teamMembers = ref<{ name: string; student_number: string }[]>(
|
|||
const newMemberName = ref("");
|
||||
const newMemberNumber = ref("");
|
||||
|
||||
function normaliseUrl(url: string): string {
|
||||
const trimmed = url.trim();
|
||||
if (trimmed && !/^https?:\/\//i.test(trimmed)) return `https://${trimmed}`;
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function handleSave() {
|
||||
const updates: { title?: string; content?: string; metadata?: Record<string, unknown> } = {};
|
||||
if (title.value !== props.segment.title) updates.title = title.value;
|
||||
if (content.value !== props.segment.content) updates.content = content.value;
|
||||
const savedContent =
|
||||
props.segment.type === "iframe" ? normaliseUrl(content.value) : content.value;
|
||||
if (savedContent !== props.segment.content) updates.content = savedContent;
|
||||
if (props.segment.type === "team") {
|
||||
metadata.value = { ...metadata.value, members: teamMembers.value };
|
||||
}
|
||||
|
|
@ -86,13 +94,17 @@ const assetAcceptMap: Record<string, string> = {
|
|||
<RichTextEditor v-if="segment.type === 'markdown'" v-model="content" />
|
||||
|
||||
<!-- Iframe: URL input -->
|
||||
<div v-else-if="segment.type === 'iframe'">
|
||||
<input
|
||||
v-else-if="segment.type === 'iframe'"
|
||||
v-model="content"
|
||||
type="url"
|
||||
placeholder="https://..."
|
||||
class="w-full rounded border border-slate-300 px-3 py-2 text-sm focus:border-primary-400 focus:outline-none dark:border-slate-600 dark:bg-slate-700 dark:text-slate-100"
|
||||
/>
|
||||
<p class="mt-1 text-xs text-slate-400 dark:text-slate-500">
|
||||
Most major sites (Google, GitHub, etc.) block embedding. Use dedicated embed URLs where available, e.g. YouTube's <code>youtube.com/embed/…</code>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- PDF / Video / Audio: file upload + URL display -->
|
||||
<div v-else-if="segment.type === 'pdf' || segment.type === 'video' || segment.type === 'audio'">
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ const { pages, fetchPages } = usePages();
|
|||
const { isAdmin, authHeaders } = useAdmin();
|
||||
|
||||
const mobileNavOpen = ref(false);
|
||||
const scrolledDown = ref(false);
|
||||
const mainEl = ref<HTMLElement | null>(null);
|
||||
const siteTitle = ref("Untitled Site");
|
||||
const editingSiteTitle = ref(false);
|
||||
const siteTitleInput = ref("");
|
||||
|
|
@ -63,6 +65,14 @@ watch(pages, (loaded) => {
|
|||
}
|
||||
});
|
||||
|
||||
function handleScroll(e: Event) {
|
||||
scrolledDown.value = (e.target as HTMLElement).scrollTop > 200;
|
||||
}
|
||||
|
||||
function scrollToTop() {
|
||||
mainEl.value?.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([loadSiteTitle(), fetchPages()]);
|
||||
if (pages.value.length > 0 && (route.path === "/" || route.path === "")) {
|
||||
|
|
@ -145,13 +155,30 @@ onMounted(async () => {
|
|||
</div>
|
||||
|
||||
<!-- Scrollable content -->
|
||||
<main class="flex-1 overflow-y-auto">
|
||||
<main ref="mainEl" class="flex-1 overflow-y-auto" @scroll="handleScroll">
|
||||
<div class="mx-auto max-w-4xl px-6 py-10">
|
||||
<slot />
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scroll to top FAB -->
|
||||
<Teleport to="body">
|
||||
<Transition name="scroll-top-fade">
|
||||
<button
|
||||
v-if="scrolledDown"
|
||||
class="fixed bottom-20 right-6 z-30 flex items-center gap-2 rounded-full bg-primary-300 px-5 py-3 font-medium text-gray-900 shadow-lg transition-all hover:bg-primary-400 hover:shadow-xl active:scale-95"
|
||||
aria-label="Scroll to top"
|
||||
@click="scrollToTop"
|
||||
>
|
||||
<svg class="h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
|
||||
</svg>
|
||||
Back to top
|
||||
</button>
|
||||
</Transition>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
|
@ -172,4 +199,13 @@ onMounted(async () => {
|
|||
.sidebar-slide-leave-to {
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
.scroll-top-fade-enter-active,
|
||||
.scroll-top-fade-leave-active {
|
||||
transition: opacity 200ms ease;
|
||||
}
|
||||
.scroll-top-fade-enter-from,
|
||||
.scroll-top-fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ async function handleReorder(ids: string[]) {
|
|||
<!-- Floating "Add section" FAB (admin only) -->
|
||||
<Teleport to="body">
|
||||
<button
|
||||
v-if="isAdmin && currentPage && !currentPage.is_system"
|
||||
v-if="isAdmin && currentPage && currentPage.slug !== 'team'"
|
||||
class="fixed right-6 bottom-6 z-30 flex items-center gap-2 rounded-full bg-primary-300 px-5 py-3 font-medium text-gray-900 shadow-lg transition-all hover:bg-primary-400 hover:shadow-xl active:scale-95"
|
||||
title="Add a new section to this page"
|
||||
@click="showAddModal = true"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue