feat: migrate dashboard to svelte 5 (#577)
* chore: migration to SvelteKit 2 * stage 1: migrate to svelte 5 and fix rerendering issues * stage 2: fix more $: reactivity across dashboard * fix merge conflicts * chore: migrate code in main to svelte 5 * chore: update preventDefault * chore: simplify tsconfig and remove unused mobileMenuRef in MobileMenu.svelte * chore: enforce .ts across dashboard * rename from classroomio-com to website * stage-3: Replace carbon icon svelte with lucide icon * update lock file * chore: finish icon replacement * fix: settings and setup page multiple rerender * stage 3: More icon and rerendering fixes * feat: checkpoint * feat: update editor * [not working] connect everything with ui build * storybook + ui + dashboard all working independently * add bg-black to +page.svelte * feat: add editor to storybook * chore: animate lucid icons (#590) * v1 animations * feat: change some course navigation icons & animate org navigation icons * chore: change settings icon * chore: use css instead of js for hover * rename animations --------- Co-authored-by: tunny17 <[email protected]> * chore: update path to node_modules types in api tsconfig * fix: update import path for DropdownMenu component * fix: add rollupOptions to build configuration in vite.config.ts * fix: remove rollupOptions from build configuration and add @floating-ui/dom dependency * install --------- Co-authored-by: tunny17 <[email protected]>
This commit is contained in:
@@ -23,7 +23,7 @@
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
import TabContent from '$lib/components/TabContent/index.svelte';
|
||||
import Tabs from '$lib/components/Tabs/index.svelte';
|
||||
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
// import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
import MODES from '$lib/utils/constants/mode';
|
||||
import { formatYoutubeVideo } from '$lib/utils/functions/formatYoutubeVideo';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
@@ -67,16 +67,27 @@
|
||||
toggleMode = () => {}
|
||||
}: Props = $props();
|
||||
|
||||
// let content = $state<EdraEditorProps['Content']>();
|
||||
// let editor = $state<EdraEditorProps['Editor']>();
|
||||
// function onUpdate() {
|
||||
// content = editor?.getHTML();
|
||||
// console.log('content updated:', content);
|
||||
// if (mode === MODES.view) return;
|
||||
|
||||
// $lessonByTranslation[lessonId][$lesson.locale] = content;
|
||||
|
||||
// try {
|
||||
// localStorage.setItem(`lesson-${lessonId}-${$lesson.locale}`, content);
|
||||
// } catch (error) {}
|
||||
// $isLessonDirty = true;
|
||||
// }
|
||||
|
||||
let localeExists: Record<string, boolean> = {};
|
||||
// let prevContent = '';
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
let errors: Record<string, string> = {};
|
||||
let editorWindowRef: Window | undefined = $state();
|
||||
let aiButtonRef: HTMLDivElement | undefined = $state();
|
||||
let openPopover = $state(false);
|
||||
let player: HTMLVideoElement | undefined = $state();
|
||||
let aiButtonClass =
|
||||
'flex items-center gap-2 px-5 py-2 border border-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md w-full mb-2';
|
||||
// let editorWindowRef: Window | undefined = $state();
|
||||
('flex items-center gap-2 px-5 py-2 border border-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md w-full mb-2');
|
||||
|
||||
const lessonTitle = $derived($lesson.title);
|
||||
|
||||
@@ -324,7 +335,7 @@
|
||||
initPlyr(player, $lesson.materials.videos);
|
||||
});
|
||||
|
||||
let editorValue = $derived(
|
||||
let _editorValue = $derived(
|
||||
lessonFallbackNote($lesson.materials.note, $lessonByTranslation[lessonId], $lesson.locale)
|
||||
);
|
||||
</script>
|
||||
@@ -403,7 +414,26 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-5 h-[60vh]">
|
||||
<TextEditor
|
||||
<!-- <div class="bg-background z-50 mt-12 size-full max-w-5xl rounded-md border border-dashed">
|
||||
{#if editor && !editor.isDestroyed}
|
||||
<EdraToolBar
|
||||
class="bg-secondary/50 flex w-full items-center overflow-x-auto border-b border-dashed p-0.5"
|
||||
{editor}
|
||||
/>
|
||||
{:else}
|
||||
<div>
|
||||
Debug: Editor state - {editor ? 'exists' : 'undefined'}, destroyed: {editor?.isDestroyed || 'N/A'}
|
||||
</div>
|
||||
{/if}
|
||||
<EdraEditor
|
||||
bind:editor
|
||||
content={editorValue}
|
||||
autofocus={true}
|
||||
class="h-[300px] outline-none"
|
||||
{onUpdate}
|
||||
/>
|
||||
</div> -->
|
||||
<!-- <TextEditor
|
||||
id={lessonId}
|
||||
bind:editorWindowRef
|
||||
value={editorValue}
|
||||
@@ -419,7 +449,7 @@
|
||||
$isLessonDirty = true;
|
||||
}}
|
||||
placeholder={$t('course.navItem.lessons.materials.tabs.note.placeholder')}
|
||||
/>
|
||||
/> -->
|
||||
</div>
|
||||
</TabContent>
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
|
||||
<div class={className}>
|
||||
<button
|
||||
class="item relative flex items-center {btnPadding} mb-1 ml-2 {NavClasses.item} {isGroupActive &&
|
||||
class="item group relative flex items-center {btnPadding} mb-1 ml-2 {NavClasses.item} {isGroupActive &&
|
||||
!isLoading &&
|
||||
NavClasses.active} w-[95%]"
|
||||
tabindex="0"
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script lang="ts">
|
||||
import TableOfContentsIcon from '@lucide/svelte/icons/table-of-contents'; // overview
|
||||
import TableOfContentsIcon from '@lucide/svelte/icons/table-of-contents';
|
||||
import BookCheckIcon from '@lucide/svelte/icons/book-check';
|
||||
import ListTodoIcon from '@lucide/svelte/icons/list-todo';
|
||||
import UsersIcon from '@lucide/svelte/icons/users';
|
||||
import ScreenShareIcon from '@lucide/svelte/icons/screen-share';
|
||||
import AwardIcon from '@lucide/svelte/icons/award';
|
||||
import ScrollTextIcon from '@lucide/svelte/icons/scroll-text';
|
||||
import SettingsIcon from '@lucide/svelte/icons/settings';
|
||||
import MegaphoneIcon from '@lucide/svelte/icons/megaphone';
|
||||
import ChartLineIcon from '@lucide/svelte/icons/chart-line';
|
||||
import ListIcon from '@lucide/svelte/icons/list';
|
||||
import SquareLibrary from '@lucide/svelte/icons/square-library';
|
||||
import BookOpenCheck from '@lucide/svelte/icons/book-open-check';
|
||||
import PanelTop from '@lucide/svelte/icons/panel-top';
|
||||
import { NAV_IDS } from './constants';
|
||||
|
||||
interface Props {
|
||||
@@ -20,25 +20,25 @@
|
||||
</script>
|
||||
|
||||
{#if name === NAV_IDS.SECTION}
|
||||
<TableOfContentsIcon size={16} />
|
||||
<TableOfContentsIcon size={16} class="group-hover:animate-flip-book" />
|
||||
{:else if name === NAV_IDS.NEWS_FEED}
|
||||
<MegaphoneIcon size={16} />
|
||||
<MegaphoneIcon size={16} class="group-hover:animate-shake-announcement" />
|
||||
{:else if name === NAV_IDS.LESSONS}
|
||||
<ScrollTextIcon size={16} />
|
||||
<ScrollTextIcon size={16} class="group-hover:animate-scroll-unroll" />
|
||||
{:else if name === NAV_IDS.ATTENDANCE}
|
||||
<ListIcon size={16} />
|
||||
<BookOpenCheck size={16} class="group-hover:animate-check-items" />
|
||||
{:else if name === NAV_IDS.SUBMISSIONS}
|
||||
<ListTodoIcon size={16} />
|
||||
<SquareLibrary size={16} class="group-hover:animate-task-complete" />
|
||||
{:else if name === NAV_IDS.MARKS}
|
||||
<BookCheckIcon size={16} />
|
||||
<BookCheckIcon size={16} class="group-hover:animate-grade-glow" />
|
||||
{:else if name === NAV_IDS.PEOPLE}
|
||||
<UsersIcon size={16} />
|
||||
<UsersIcon size={16} class="group-hover:animate-people-wave" />
|
||||
{:else if name === NAV_IDS.ANALYTICS}
|
||||
<ChartLineIcon size={16} />
|
||||
<ChartLineIcon size={16} class="group-hover:animate-chart-grow" />
|
||||
{:else if name === NAV_IDS.LANDING_PAGE}
|
||||
<ScreenShareIcon size={16} />
|
||||
<PanelTop size={16} class="group-hover:animate-screen-glow" />
|
||||
{:else if name === NAV_IDS.CERTIFICATES}
|
||||
<AwardIcon size={16} />
|
||||
<AwardIcon size={16} class="group-hover:animate-bounce" />
|
||||
{:else if name === NAV_IDS.SETTINGS}
|
||||
<SettingsIcon size={16} />
|
||||
<SettingsIcon size={16} class="group-hover:animate-spin" />
|
||||
{/if}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
const result = (await response.json());
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || $t('courses.copy_course.error.failed_to_clone'));
|
||||
|
||||
@@ -34,11 +34,6 @@
|
||||
emptyDescription = $t('courses.course_card.empty_description'),
|
||||
isExplore = false
|
||||
}: Props = $props();
|
||||
|
||||
export let courses: Course[] = [];
|
||||
export let emptyTitle = $t('courses.course_card.empty_title');
|
||||
export let emptyDescription = $t('courses.course_card.empty_description');
|
||||
export let isExplore = false;
|
||||
|
||||
function calcProgressRate(progressRate?: number, totalLessons?: number): number {
|
||||
if (!progressRate || !totalLessons) {
|
||||
@@ -55,19 +50,19 @@
|
||||
|
||||
try {
|
||||
await deleteCourse($deleteCourseModal.id);
|
||||
|
||||
|
||||
// Remove the course from the courses store
|
||||
$coursesStore = $coursesStore.filter(course => course.id !== $deleteCourseModal.id);
|
||||
|
||||
$coursesStore = $coursesStore.filter((course) => course.id !== $deleteCourseModal.id);
|
||||
|
||||
// Show success message
|
||||
snackbar.success('snackbar.course_deleted');
|
||||
|
||||
|
||||
// Close modal and reset state
|
||||
deleteCourseModal.set(deleteCourseModalInitialState);
|
||||
} catch (error) {
|
||||
console.error('Error deleting course:', error);
|
||||
snackbar.error('snackbar.course_settings.error.went_wrong');
|
||||
|
||||
|
||||
// Stop deleting state on error
|
||||
$deleteCourseModal.isDeleting = false;
|
||||
}
|
||||
@@ -75,8 +70,8 @@
|
||||
</script>
|
||||
|
||||
<CopyCourseModal />
|
||||
<DeleteModal
|
||||
onDelete={handleDeleteCourse}
|
||||
<DeleteModal
|
||||
onDelete={handleDeleteCourse}
|
||||
bind:open={$deleteCourseModal.open}
|
||||
isLoading={$deleteCourseModal.isDeleting}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { Editor } from '@cio/ui/custom/editor';
|
||||
import defaultContent from './default-content';
|
||||
</script>
|
||||
|
||||
<Editor content={defaultContent} />
|
||||
@@ -0,0 +1,825 @@
|
||||
export default {
|
||||
type: 'doc',
|
||||
content: [
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: 'center',
|
||||
level: 1
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Introducing ClassroomIO'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: 'justify',
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'The AI native LMS. We have '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'bold'
|
||||
}
|
||||
],
|
||||
text: 'bold'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'italic'
|
||||
}
|
||||
],
|
||||
text: 'italic'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'strike'
|
||||
}
|
||||
],
|
||||
text: 'strike'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'underline'
|
||||
}
|
||||
],
|
||||
text: 'underline'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'link',
|
||||
attrs: {
|
||||
href: 'https://github.com',
|
||||
target: '_blank',
|
||||
rel: 'noopener noreferrer',
|
||||
class: null
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'link'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'code'
|
||||
}
|
||||
],
|
||||
text: 'code'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', Superscript A'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'superscript'
|
||||
}
|
||||
],
|
||||
text: 'b'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', subscript A'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'subscript'
|
||||
}
|
||||
],
|
||||
text: 'b'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'highlight',
|
||||
attrs: {
|
||||
color: '#800080'
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'highlight1'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'highlight',
|
||||
attrs: {
|
||||
color: '#1a1600'
|
||||
}
|
||||
}
|
||||
],
|
||||
text: ','
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'highlight',
|
||||
attrs: {
|
||||
color: '#0000FF'
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'highlight2'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'textStyle',
|
||||
attrs: {
|
||||
color: '#008000',
|
||||
fontSize: ''
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'textcolor1'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'textStyle',
|
||||
attrs: {
|
||||
color: 'rgb(78, 211, 21)',
|
||||
fontSize: ''
|
||||
}
|
||||
}
|
||||
],
|
||||
text: ', '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'textStyle',
|
||||
attrs: {
|
||||
color: '#FFA500',
|
||||
fontSize: ''
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'textcolor2'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'blockquote',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: 'justify',
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'This is a block Quote'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'codeBlock',
|
||||
attrs: {
|
||||
language: 'javascript'
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'console.log("Hello World")'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'horizontalRule'
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: 'left',
|
||||
level: 2
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Support For Media Placeholders and Medias'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'image-placeholder'
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
attrs: {
|
||||
src: 'https://placehold.co/800x400/6A00F5/white',
|
||||
alt: null,
|
||||
title: 'A Sample Image',
|
||||
width: 658,
|
||||
height: null,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'video-placeholder'
|
||||
},
|
||||
{
|
||||
type: 'video',
|
||||
attrs: {
|
||||
src: 'https://videos.pexels.com/video-files/2491284/2491284-uhd_2732_1440_24fps.mp4',
|
||||
alt: null,
|
||||
title: 'A Sample Video',
|
||||
width: 673,
|
||||
height: null,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'audio-placeholder'
|
||||
},
|
||||
{
|
||||
type: 'audio',
|
||||
attrs: {
|
||||
src: 'https://cdn.pixabay.com/audio/2024/10/30/audio_42e6870f29.mp3',
|
||||
alt: null,
|
||||
title: 'A Sample Audio',
|
||||
width: 670,
|
||||
height: null,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'horizontalRule'
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
level: 2
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'IFrames'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'iframe-placeholder'
|
||||
},
|
||||
{
|
||||
type: 'iframe',
|
||||
attrs: {
|
||||
src: 'https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d3748.5645078258503!2d75.17451197505285!3d20.026784381383496!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3bdb93bd138ae4bd%3A0x574c6482cf0b89cf!2sEllora%20Caves!5e0!3m2!1sen!2sin!4v1742466514831!5m2!1sen!2sin',
|
||||
alt: null,
|
||||
title: 'Ellora Caves, Maharastra, India',
|
||||
width: 621,
|
||||
height: null,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'iframe',
|
||||
attrs: {
|
||||
src: 'https://www.youtube.com/embed/jfKfPfyJRdk?si=SYBE0PkI5v79-54W',
|
||||
alt: null,
|
||||
title: 'Lofi Hip Hop Youtube Streaming Video',
|
||||
width: 623,
|
||||
height: null,
|
||||
align: 'center'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'horizontalRule'
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
level: 2
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Typography'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Smilipicker, type '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'code'
|
||||
}
|
||||
],
|
||||
text: ':)'
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' and it becomes 🙂 or 😉. Typography support e.g. type '
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'code'
|
||||
}
|
||||
],
|
||||
text: '!='
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' and it becomes ≠. Similarly (c) becomes ©, -> becomes → and many more like 1×2, ½. We also have color visualizer. #FFF, #000, #FF00FF can be visualized.'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'horizontalRule'
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
level: 2
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Tables'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'table',
|
||||
content: [
|
||||
{
|
||||
type: 'tableRow',
|
||||
content: [
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [323],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [328],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [330],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'tableRow',
|
||||
content: [
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [323],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [328],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'tableCell',
|
||||
attrs: {
|
||||
colspan: 1,
|
||||
rowspan: 1,
|
||||
colwidth: [330],
|
||||
style: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Hello World'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
level: 3
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
marks: [
|
||||
{
|
||||
type: 'textStyle',
|
||||
attrs: {
|
||||
color: 'var(--tw-prose-headings)',
|
||||
fontSize: '1.5em'
|
||||
}
|
||||
}
|
||||
],
|
||||
text: 'Rending Math and '
|
||||
},
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex: '\\LaTeX',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'The editor supports the '
|
||||
},
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex: '\\LaTeX',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
text: ' rendering. e.g.'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'orderedList',
|
||||
attrs: {
|
||||
start: 1
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Functions like, '
|
||||
},
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex: 'sin²\\theta + cos²\\theta=1, a²+b²=c²',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Matrix like unit matrix'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex: '\\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'or more matrices like'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex:
|
||||
'\\begin{pmatrix} 1 & 0 & \\cdots & 0 \\\\ 0 & 1 & \\cdots & 0 \\\\ \\vdots & \\vdots & \\ddots & \\vdots \\\\ 0 & 0 & \\cdots & 1 \\end{pmatrix}',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'listItem',
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Integrals and differential'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex:
|
||||
'\\int_{0}^{\\frac{\\pi}{2}} \\sin(x) \\,dx = \\left[-\\cos(x)\\right]_0^{\\frac{\\pi}{2}} = -\\cos\\left(\\frac{\\pi}{2}\\right) + \\cos(0)',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'inlineMath',
|
||||
attrs: {
|
||||
latex: '\\frac{\\mathrm{d}}{\\mathrm{d}x}(\\sin x) = \\cos x',
|
||||
evaluate: 'no',
|
||||
display: 'no'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'heading',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
level: 3
|
||||
},
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: 'Explore More…'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'paragraph',
|
||||
attrs: {
|
||||
textAlign: null,
|
||||
class: null
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -11,11 +11,6 @@
|
||||
}
|
||||
|
||||
let { open = $bindable(false), onDelete = () => {}, isLoading = false }: Props = $props();
|
||||
|
||||
function handleDelete() {
|
||||
onDelete();
|
||||
open = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { browser } from '$app/environment';
|
||||
import BellIcon from '@lucide/svelte/icons/bell';
|
||||
import MoonIcon from '@lucide/svelte/icons/moon';
|
||||
import SunIcon from '@lucide/svelte/icons/sun';
|
||||
import MenuIcon from '@lucide/svelte/icons/menu';
|
||||
import XIcon from '@lucide/svelte/icons/x';
|
||||
import ArrowLeftIcon from '@lucide/svelte/icons/arrow-left';
|
||||
import { ModeSwitcher } from '@cio/ui/base/dark-mode';
|
||||
|
||||
import { IconButton } from '$lib/components/IconButton';
|
||||
import { globalStore } from '$lib/utils/store/app';
|
||||
import { currentOrgPath } from '$lib/utils/store/org';
|
||||
import { toggleBodyByMode } from '$lib/utils/functions/app';
|
||||
import { sideBar } from '../Org/store';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
@@ -27,16 +24,6 @@
|
||||
$sideBar.hidden = !$sideBar.hidden;
|
||||
};
|
||||
|
||||
function toggleDarkMode() {
|
||||
$globalStore.isDark = !$globalStore.isDark;
|
||||
|
||||
toggleBodyByMode($globalStore.isDark);
|
||||
|
||||
if (browser) {
|
||||
localStorage.setItem('mode', $globalStore.isDark ? 'dark' : '');
|
||||
}
|
||||
}
|
||||
|
||||
let coursesPath = $derived(
|
||||
$globalStore.isOrgSite ? '/lms/mylearning' : isCoursePage ? `${$currentOrgPath}/courses` : $currentOrgPath
|
||||
);
|
||||
@@ -91,13 +78,14 @@
|
||||
<BellIcon class="custom text-white" size={16} />
|
||||
</li>
|
||||
<li>
|
||||
<IconButton size="small" onClick={toggleDarkMode}>
|
||||
<!-- <IconButton size="small" onClick={toggleDarkMode}>
|
||||
{#if $globalStore.isDark}
|
||||
<SunIcon size={16} class="custom" />
|
||||
{:else}
|
||||
<MoonIcon class="custom text-white" size={16} />
|
||||
{/if}
|
||||
</IconButton>
|
||||
</IconButton> -->
|
||||
<ModeSwitcher />
|
||||
</li>
|
||||
|
||||
<li></li>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { isCoursePage } from '$lib/utils/functions/app';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import type { TCustomLinks } from './types';
|
||||
import { ModeSwitcher } from '@cio/ui/base/dark-mode';
|
||||
|
||||
import Logo from './Logo.svelte';
|
||||
import CustomLinks from './CustomLinks.svelte';
|
||||
@@ -76,6 +77,8 @@
|
||||
{:else if !isOrgSite && !$page.url.pathname?.includes('/404')}
|
||||
<AuthButtons {disableSignup} {redirect} />
|
||||
{/if}
|
||||
|
||||
<ModeSwitcher />
|
||||
</ul>
|
||||
|
||||
{#if showLinks}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script>
|
||||
import { Popover } from 'carbon-components-svelte';
|
||||
import Menu from './Menu.svelte';
|
||||
import Modal from '$lib/components/Modal/index.svelte';
|
||||
|
||||
import { isMobile } from '$lib/utils/store/useMobile';
|
||||
import { profileMenu } from '../store';
|
||||
import { clickOutside } from '$lib/utils/functions/routes/dashboard';
|
||||
</script>
|
||||
|
||||
{#if $isMobile}
|
||||
@@ -16,15 +17,12 @@
|
||||
>
|
||||
<Menu />
|
||||
</Modal>
|
||||
{:else}
|
||||
<Popover
|
||||
bind:open={$profileMenu.open}
|
||||
align="right-bottom"
|
||||
caret={true}
|
||||
on:click:outside={({ detail }) => {
|
||||
$profileMenu.open = !!$profileMenu.ref?.contains(detail.target);
|
||||
}}
|
||||
{:else if $profileMenu.open}
|
||||
<div
|
||||
use:clickOutside
|
||||
on:click:outside={() => ($profileMenu.open = false)}
|
||||
class="absolute bottom-28 left-0 z-[300] w-full border bg-black p-2 dark:bg-white"
|
||||
>
|
||||
<Menu />
|
||||
</Popover>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -76,10 +76,11 @@
|
||||
if (error) throw error;
|
||||
} catch (error) {
|
||||
let message = error instanceof Error ? error.message : `${error}`;
|
||||
|
||||
if (message.includes('profile_username_key')) {
|
||||
message = $t('snackbar.lms.error.username_exists');
|
||||
}
|
||||
|
||||
|
||||
snackbar.error(`${$t('snackbar.lms.error.update')}: ${message}`);
|
||||
|
||||
loading = false;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import LibraryBigIcon from '@lucide/svelte/icons/library-big';
|
||||
import UsersIcon from '@lucide/svelte/icons/users';
|
||||
import ChevronRightIcon from '@lucide/svelte/icons/chevron-right';
|
||||
import SettingsIcon from '@lucide/svelte/icons/settings';
|
||||
import Settings2 from '@lucide/svelte/icons/settings-2';
|
||||
import MessageSquareMoreIcon from '@lucide/svelte/icons/message-square-more';
|
||||
import BadgeHelpIcon from '@lucide/svelte/icons/badge-help';
|
||||
import Dice6Icon from '@lucide/svelte/icons/dice-6';
|
||||
@@ -98,24 +98,24 @@
|
||||
{#if menuItem.show}
|
||||
<a href="{$currentOrgPath}{menuItem.path}" class="text-black no-underline" onclick={toggleSidebar}>
|
||||
<li
|
||||
class="mb-1 flex items-center gap-2.5 px-2.5 py-2 {NavClasses.item} {menuItem.isActive
|
||||
class="group mb-1 flex items-center gap-2.5 px-2.5 py-2 {NavClasses.item} {menuItem.isActive
|
||||
? NavClasses.active
|
||||
: 'dark:text-white'}"
|
||||
>
|
||||
{#if menuItem.path === ''}
|
||||
<LayoutDashboardIcon size={16} />
|
||||
<LayoutDashboardIcon size={16} class="group-hover:animate-gather" />
|
||||
{:else if menuItem.path === '/courses'}
|
||||
<LibraryBigIcon size={16} />
|
||||
<LibraryBigIcon size={16} class="group-hover:animate-library-expand" />
|
||||
{:else if menuItem.path === '/site'}
|
||||
<LayoutTemplateIcon size={16} />
|
||||
<LayoutTemplateIcon size={16} class="group-hover:animate-template-morph" />
|
||||
{:else if menuItem.path === '/community'}
|
||||
<MessageSquareMoreIcon size={16} />
|
||||
<MessageSquareMoreIcon size={16} class="group-hover:animate-ripple" />
|
||||
{:else if menuItem.path === '/quiz'}
|
||||
<Dice6Icon size={16} />
|
||||
<Dice6Icon size={16} class="group-hover:animate-roll" />
|
||||
{:else if menuItem.path === '/audience'}
|
||||
<UsersIcon size={16} />
|
||||
<UsersIcon size={16} class="group-hover:animate-gather" />
|
||||
{:else if menuItem.path === '/setup'}
|
||||
<SettingsIcon size={16} />
|
||||
<Settings2 size={16} class="group-hover:animate-setup-configure" />
|
||||
{/if}
|
||||
<p class="text-sm font-medium">{menuItem.label}</p>
|
||||
</li>
|
||||
|
||||
@@ -52,3 +52,19 @@ export const formatDate = (_selectedDate: string | undefined): string => {
|
||||
|
||||
return selectedDate.format('ddd, MMM D');
|
||||
};
|
||||
|
||||
export function clickOutside(node) {
|
||||
const handleClick = (event) => {
|
||||
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
||||
node.dispatchEvent(new CustomEvent('click:outside', { detail: event.target }));
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -158,9 +158,7 @@ export const orgLandingpageValidation = (fields = {}) => {
|
||||
export const onboardingValidation = (fields = {}, step) => {
|
||||
const onboardingValidationSchema = {
|
||||
stepOne: z.object({
|
||||
fullname: z
|
||||
.string()
|
||||
.min(5, { message: `${t.get('validations.onboarding.step_one.full_name.min_char')}` }),
|
||||
fullname: z.string().min(5, { message: `${t.get('validations.onboarding.step_one.full_name.min_char')}` }),
|
||||
orgName: getOrgNameValidation(),
|
||||
siteName: getSiteNameValidation()
|
||||
}),
|
||||
@@ -178,8 +176,7 @@ export const onboardingValidation = (fields = {}, step) => {
|
||||
})
|
||||
};
|
||||
|
||||
const schema =
|
||||
step === 1 ? onboardingValidationSchema.stepOne : onboardingValidationSchema.stepTwo;
|
||||
const schema = step === 1 ? onboardingValidationSchema.stepOne : onboardingValidationSchema.stepTwo;
|
||||
const { error } = schema.safeParse(fields);
|
||||
|
||||
return processErrors(error);
|
||||
|
||||
Reference in New Issue
Block a user