feat: lesson note version history (#374)
* chore: install diff package * chore: add id to render component * feat: implement language lesson version history * chore: fix darkmode colors and use tailwind css * chore: add screensize conditional * refactor: list updating after data fetch * change mode to view to prevent duplicate data being saved * chore: remove raw text * chore: db migration * feat: properly format timestamp * feat: add snackbar for success * chore: group by minutes, use icons instead of texts for buttons * docs: generate translations * refactor --------- Co-authored-by: Iniubong Obonguko <[email protected]>
This commit is contained in:
co-authored by
Iniubong Obonguko
parent
decea8d1e5
commit
931ccb1a09
@@ -79,6 +79,7 @@
|
||||
"babel-jest": "^29.7.0",
|
||||
"carbon-components-svelte": "^0.79.0",
|
||||
"carbon-icons-svelte": "^12.1.0",
|
||||
"diff": "^5.2.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"jessy": "^3.1.1",
|
||||
|
||||
+268
@@ -0,0 +1,268 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import HtmlRender from '$lib/components/HTMLRender/HTMLRender.svelte';
|
||||
import ArrowLeft from 'carbon-icons-svelte/lib/ArrowLeft.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import CaretRight from 'carbon-icons-svelte/lib/CaretRight.svelte';
|
||||
import { fetchLesssonLanguageHistory } from '$lib/utils/services/courses';
|
||||
import { diffLines } from 'diff';
|
||||
import { lesson, lessons } from '$lib/components/Course/components/Lesson/store/lessons';
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
|
||||
import { LOCALE } from '$lib/utils/types';
|
||||
import { snackbar } from '$lib/components/Snackbar/store';
|
||||
|
||||
interface LessonHistory {
|
||||
new_content: string;
|
||||
old_content: string;
|
||||
timestamp: Date;
|
||||
locale: LOCALE;
|
||||
lesson_id: string;
|
||||
}
|
||||
|
||||
export let open = false;
|
||||
let lessonTitle: string = '';
|
||||
let lessonId: string = '';
|
||||
let lessonHistory: LessonHistory[] = [];
|
||||
let content = '';
|
||||
let selectedVersion: LessonHistory = {
|
||||
new_content: '',
|
||||
old_content: '',
|
||||
timestamp: new Date(),
|
||||
locale: LOCALE.EN,
|
||||
lesson_id: ''
|
||||
};
|
||||
let selectedVersionIndex = 0;
|
||||
let contentRestoreLoading = false;
|
||||
let versionsToFetch = 9;
|
||||
let isMoreHistoryLoading = false;
|
||||
|
||||
let mounted = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
function scrollLock(open) {
|
||||
if (mounted) {
|
||||
const body = document.querySelector('body');
|
||||
if (!body) return;
|
||||
|
||||
body.style.overflow = open ? 'hidden' : 'auto';
|
||||
}
|
||||
}
|
||||
|
||||
function formatTimestamp(timestamp) {
|
||||
const options: Intl.DateTimeFormatOptions = {
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
hour12: true
|
||||
};
|
||||
const date = new Date(timestamp + 'Z');
|
||||
return new Intl.DateTimeFormat('en-US', options).format(date);
|
||||
}
|
||||
|
||||
function handleDrawerClose() {
|
||||
dispatch('close');
|
||||
}
|
||||
|
||||
function getMinutes(time: Date) {
|
||||
return new Date(time).getMinutes();
|
||||
}
|
||||
|
||||
function removeDuplicate(history: LessonHistory[]) {
|
||||
return history.filter(
|
||||
(obj1, i, arr) =>
|
||||
arr.findIndex((obj2) => getMinutes(obj2.timestamp) === getMinutes(obj1.timestamp)) === i
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchLessonHistory(lessonId: string, locale: string, endRange: number) {
|
||||
try {
|
||||
isMoreHistoryLoading = true;
|
||||
const { data, error } = await fetchLesssonLanguageHistory(lessonId, locale, endRange);
|
||||
|
||||
if (!data) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Filter out duplicates based on timestamp
|
||||
const existingTimestamps = new Set(
|
||||
lessonHistory.map((item) => new Date(item.timestamp).getMinutes())
|
||||
);
|
||||
const newEntries = data.filter(
|
||||
(item) => !existingTimestamps.has(new Date(item.timestamp).getMinutes())
|
||||
);
|
||||
lessonHistory = removeDuplicate([...lessonHistory, ...newEntries]);
|
||||
|
||||
updateContentVersion(lessonHistory[0], 0);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
snackbar.error('Failed to fetch history');
|
||||
} finally {
|
||||
isMoreHistoryLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
scrollLock(open);
|
||||
});
|
||||
|
||||
function updateContentVersion(content: LessonHistory, index: number) {
|
||||
selectedVersionIndex = index;
|
||||
selectedVersion = content;
|
||||
const display = document.getElementById('display');
|
||||
display!.innerHTML = '';
|
||||
|
||||
const diff = diffLines(content.old_content, content.new_content);
|
||||
const fragment = document.createDocumentFragment();
|
||||
|
||||
diff.forEach((part) => {
|
||||
const span = document.createElement('span');
|
||||
if (part.added) {
|
||||
span.classList.add('text-green-500');
|
||||
} else if (part.removed) {
|
||||
span.classList.add('text-red-500', 'line-through');
|
||||
} else {
|
||||
span.classList.add('text-black', 'dark:text-white');
|
||||
}
|
||||
span.innerHTML = part.value;
|
||||
fragment.appendChild(span);
|
||||
});
|
||||
|
||||
display!.appendChild(fragment);
|
||||
}
|
||||
|
||||
async function restoreSelectedVersion() {
|
||||
try {
|
||||
contentRestoreLoading = true;
|
||||
await supabase
|
||||
.from('lesson_language')
|
||||
.update({ content: selectedVersion.new_content })
|
||||
.eq('lesson_id', selectedVersion.lesson_id)
|
||||
.eq('locale', selectedVersion.locale);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
snackbar.error('Failed to restore');
|
||||
} finally {
|
||||
contentRestoreLoading = false;
|
||||
dispatch('restore');
|
||||
}
|
||||
}
|
||||
|
||||
function loadMoreHistory() {
|
||||
versionsToFetch += 10;
|
||||
}
|
||||
|
||||
$: lessonTitle = $lessons.find((les) => les.id === $lesson.id)?.title || '';
|
||||
$: lessonId = $lesson.id || '';
|
||||
$: scrollLock(open);
|
||||
$: fetchLessonHistory(lessonId, $lesson.locale, versionsToFetch);
|
||||
</script>
|
||||
|
||||
<aside class="drawer bg-gray-100 dark:bg-neutral-800" class:open>
|
||||
<div class="panel bg-white dark:bg-black">
|
||||
<div class="p-10 w-full pr-80">
|
||||
<div class="flex items-start gap-x-10">
|
||||
<PrimaryButton variant={VARIANTS.OUTLINED} onClick={handleDrawerClose}>
|
||||
<ArrowLeft />
|
||||
</PrimaryButton>
|
||||
|
||||
{#if selectedVersionIndex != 0}
|
||||
<div class="">
|
||||
<PrimaryButton isLoading={contentRestoreLoading} onClick={restoreSelectedVersion}
|
||||
>{$t('course.navItem.lessons.version_history.restore_version')}</PrimaryButton
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="w-full h-full flex flex-col items-start">
|
||||
<HtmlRender className="m-auto text-center mt-6 flex items-center justify-center">
|
||||
<svelte:fragment slot="content">
|
||||
<h1 class="text-2xl md:text-4xl mt-0 capitalize">
|
||||
{lessonTitle}
|
||||
</h1>
|
||||
</svelte:fragment>
|
||||
</HtmlRender>
|
||||
|
||||
{#key lessonId}
|
||||
<HtmlRender id="display" className="m-auto">
|
||||
<svelte:fragment slot="content">
|
||||
<div class="amen">
|
||||
{@html content}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</HtmlRender>
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
id="scroll-container"
|
||||
class="w-80 fixed right-0 top-0 min-h-screen h-full z-10 overflow-x-auto overflow-y-scroll bg-gray-100 dark:bg-neutral-800 py-10 space-y-6"
|
||||
>
|
||||
<p class="font-medium text-xl text-left flex items-start justify-start px-10">
|
||||
{$t('course.navItem.lessons.version_history.title')}
|
||||
</p>
|
||||
|
||||
<div>
|
||||
{#each lessonHistory as version, index}
|
||||
<button
|
||||
on:click={() => updateContentVersion(version, index)}
|
||||
class="hover:bg-gray-200 dark:hover:bg-neutral-700 cursor-pointer p-4 w-full px-10 flex items-start {index ==
|
||||
selectedVersionIndex
|
||||
? 'bg-gray-200 dark:bg-neutral-700'
|
||||
: ''}"
|
||||
>
|
||||
<CaretRight class="mt-1"></CaretRight>
|
||||
<div>
|
||||
<span class="inline-block font-medium text-base"
|
||||
>{formatTimestamp(version.timestamp)}</span
|
||||
>
|
||||
{#if index == 0}
|
||||
<span class="block italic text-xs text-start"
|
||||
>{$t('course.navItem.lessons.version_history.current_version')}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</button>
|
||||
{/each}
|
||||
<div class="flex items-center justify-start px-10 mt-2 h-10">
|
||||
<PrimaryButton className="h-full" isLoading={isMoreHistoryLoading} onClick={loadMoreHistory}
|
||||
>{$t('course.navItem.lessons.version_history.fetch_more_versions')}</PrimaryButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<style>
|
||||
.drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.drawer.open {
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.panel {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 3;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.drawer.open .panel {
|
||||
transform: translate(0, 0);
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,10 @@
|
||||
<script>
|
||||
export let content = '';
|
||||
export let className = '';
|
||||
export let id = '';
|
||||
</script>
|
||||
|
||||
<article class="preview prose prose-sm sm:prose {className} dark:text-white">
|
||||
<article {id} class="preview prose prose-sm sm:prose {className} dark:text-white">
|
||||
{#if $$slots.content}
|
||||
<slot name="content" />
|
||||
{:else}
|
||||
|
||||
@@ -185,6 +185,16 @@ export function fetchLesson(lessonId: Lesson['id']) {
|
||||
.single();
|
||||
}
|
||||
|
||||
export function fetchLesssonLanguageHistory(lessonId: string, locale: string, endRange: number) {
|
||||
return supabase
|
||||
.from('lesson_versions')
|
||||
.select('*')
|
||||
.range(0, endRange)
|
||||
.eq('lesson_id', lessonId)
|
||||
.eq('locale', locale)
|
||||
.order('timestamp', { ascending: false });
|
||||
}
|
||||
|
||||
export function createLesson(lesson: any) {
|
||||
return supabase.from('lesson').insert(lesson).select();
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
},
|
||||
"success": {
|
||||
"complete_marked": "Als abgeschlossen markiert",
|
||||
"complete_download": "Download abgeschlossen"
|
||||
"complete_download": "Download abgeschlossen",
|
||||
"version_restored": "Version erfolgreich wiederhergestellt"
|
||||
}
|
||||
},
|
||||
"attendance": {
|
||||
@@ -750,6 +751,12 @@
|
||||
"disabled": "Funktion ist deaktiviert",
|
||||
"next": "Nächste",
|
||||
"prev": "Vorher",
|
||||
"version_history": {
|
||||
"title": "Versionsgeschichte",
|
||||
"fetch_more_versions": "Weitere Versionen abrufen",
|
||||
"current_version": "Aktuelle Version",
|
||||
"restore_version": "Stellen Sie diese Version wieder her"
|
||||
},
|
||||
"drag": "Ziehen Sie eine Karte, um sie neu anzuordnen",
|
||||
"no_lesson": "Noch kein Unterricht",
|
||||
"share_your_knowledge": "Teilen Sie Ihr Wissen mit der Welt, indem Sie ansprechende Lektionen erstellen. Klicken Sie zunächst auf die Schaltfläche „Hinzufügen“."
|
||||
@@ -1363,4 +1370,4 @@
|
||||
"hyphen_rule": "Der Name der Organisation darf nicht mit einem Bindestrich beginnen oder enden"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -747,6 +747,12 @@
|
||||
"option_label": "Option Label",
|
||||
"cancel": "Cancel",
|
||||
"poll_question": "Poll Question"
|
||||
},
|
||||
"version_history": {
|
||||
"title": "Version history",
|
||||
"fetch_more_versions": "Fetch more versions",
|
||||
"current_version": "Current version",
|
||||
"restore_version": "Restore this version"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1243,7 +1249,8 @@
|
||||
"lessons": {
|
||||
"success": {
|
||||
"complete_marked": "Marked as complete",
|
||||
"complete_download": "Download Complete"
|
||||
"complete_download": "Download Complete",
|
||||
"version_restored": "Version restored sucessfully"
|
||||
},
|
||||
"error": {
|
||||
"try_later": "Something's not right - Please try later"
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
},
|
||||
"success": {
|
||||
"complete_marked": "Marcado como completo",
|
||||
"complete_download": "Descarga completa"
|
||||
"complete_download": "Descarga completa",
|
||||
"version_restored": "Versión restaurada exitosamente"
|
||||
}
|
||||
},
|
||||
"attendance": {
|
||||
@@ -750,6 +751,12 @@
|
||||
"disabled": "La característica está deshabilitada",
|
||||
"next": "Próximo",
|
||||
"prev": "Anterior",
|
||||
"version_history": {
|
||||
"title": "Historial de versiones",
|
||||
"fetch_more_versions": "Obtener más versiones",
|
||||
"current_version": "Versión actual",
|
||||
"restore_version": "Restaurar esta versión"
|
||||
},
|
||||
"drag": "arrastra una tarjeta para reordenar",
|
||||
"no_lesson": "Aún no hay lecciones",
|
||||
"share_your_knowledge": "Comparta sus conocimientos con el mundo creando lecciones interesantes. Comience haciendo clic en el botón Agregar."
|
||||
@@ -1363,4 +1370,4 @@
|
||||
"hyphen_rule": "El nombre de la organización no puede comenzar ni terminar con un guión."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
},
|
||||
"success": {
|
||||
"complete_marked": "Marqué comme terminé",
|
||||
"complete_download": "Téléchargement complet"
|
||||
"complete_download": "Téléchargement complet",
|
||||
"version_restored": "Version restaurée avec succès"
|
||||
}
|
||||
},
|
||||
"attendance": {
|
||||
@@ -750,6 +751,12 @@
|
||||
"disabled": "La fonctionnalité est désactivée",
|
||||
"next": "Suivant",
|
||||
"prev": "Précédent",
|
||||
"version_history": {
|
||||
"title": "Historique des versions",
|
||||
"fetch_more_versions": "Récupérer plus de versions",
|
||||
"current_version": "Version actuelle",
|
||||
"restore_version": "Restaurer cette version"
|
||||
},
|
||||
"drag": "faites glisser une carte pour réorganiser",
|
||||
"no_lesson": "Pas encore de cours",
|
||||
"share_your_knowledge": "Partagez vos connaissances avec le monde en créant des leçons engageantes. Commencez par cliquer sur le bouton Ajouter."
|
||||
@@ -1363,4 +1370,4 @@
|
||||
"hyphen_rule": "Le nom de l'organisation ne peut pas commencer ou se terminer par un trait d'union"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
},
|
||||
"success": {
|
||||
"complete_marked": "पूर्ण के रूप में चिह्नित",
|
||||
"complete_download": "डाउनलोड पूर्ण"
|
||||
"complete_download": "डाउनलोड पूर्ण",
|
||||
"version_restored": "संस्करण सफलतापूर्वक पुनर्स्थापित किया गया"
|
||||
}
|
||||
},
|
||||
"attendance": {
|
||||
@@ -750,6 +751,12 @@
|
||||
"disabled": "सुविधा अक्षम है",
|
||||
"next": "अगला",
|
||||
"prev": "पिछला",
|
||||
"version_history": {
|
||||
"title": "संस्करण इतिहास",
|
||||
"fetch_more_versions": "अधिक संस्करण प्राप्त करें",
|
||||
"current_version": "वर्तमान संस्करण",
|
||||
"restore_version": "इस संस्करण को पुनर्स्थापित करें"
|
||||
},
|
||||
"drag": "पुनः व्यवस्थित करने के लिए कार्ड खींचें",
|
||||
"no_lesson": "अभी तक कोई सबक नहीं",
|
||||
"share_your_knowledge": "आकर्षक पाठ बनाकर अपना ज्ञान दुनिया के साथ साझा करें। ऐड बटन पर क्लिक करके प्रारंभ करें।"
|
||||
@@ -1363,4 +1370,4 @@
|
||||
"hyphen_rule": "संगठन का नाम हाइफ़न से प्रारंभ या समाप्त नहीं हो सकता"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
},
|
||||
"success": {
|
||||
"complete_marked": "Marcado como concluído",
|
||||
"complete_download": "Download completo"
|
||||
"complete_download": "Download completo",
|
||||
"version_restored": "Versão restaurada com sucesso"
|
||||
}
|
||||
},
|
||||
"attendance": {
|
||||
@@ -750,6 +751,12 @@
|
||||
"disabled": "O recurso está desativado",
|
||||
"next": "Próximo",
|
||||
"prev": "Anterior",
|
||||
"version_history": {
|
||||
"title": "Histórico de versões",
|
||||
"fetch_more_versions": "Buscar mais versões",
|
||||
"current_version": "Versão Atual",
|
||||
"restore_version": "Restaurar esta versão"
|
||||
},
|
||||
"drag": "arraste um cartão para reordenar",
|
||||
"no_lesson": "Ainda não há aulas",
|
||||
"share_your_knowledge": "Compartilhe seu conhecimento com o mundo criando aulas envolventes. Comece clicando no botão Adicionar."
|
||||
@@ -1363,4 +1370,4 @@
|
||||
"hyphen_rule": "O nome da organização não pode começar ou terminar com um hífen"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
},
|
||||
"success": {
|
||||
"complete_marked": "Помечено как завершенное",
|
||||
"complete_download": "Загрузка завершена"
|
||||
"complete_download": "Загрузка завершена",
|
||||
"version_restored": "Версия успешно восстановлена"
|
||||
}
|
||||
},
|
||||
"attendance": {
|
||||
@@ -750,6 +751,12 @@
|
||||
"disabled": "Функция отключена",
|
||||
"next": "Следующий",
|
||||
"prev": "Предыдущий",
|
||||
"version_history": {
|
||||
"title": "История версий",
|
||||
"fetch_more_versions": "Получить больше версий",
|
||||
"current_version": "Текущая версия",
|
||||
"restore_version": "Восстановить эту версию"
|
||||
},
|
||||
"drag": "перетащите карточку, чтобы изменить порядок",
|
||||
"no_lesson": "Уроков пока нет",
|
||||
"share_your_knowledge": "Поделитесь своими знаниями со всем миром, создавая увлекательные уроки. Начните с нажатия кнопки «Добавить»."
|
||||
@@ -1363,4 +1370,4 @@
|
||||
"hyphen_rule": "Название организации не может начинаться или заканчиваться дефисом."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@
|
||||
},
|
||||
"success": {
|
||||
"complete_marked": "Được đánh dấu là hoàn thành",
|
||||
"complete_download": "Hoan thanh tải"
|
||||
"complete_download": "Hoan thanh tải",
|
||||
"version_restored": "Đã khôi phục phiên bản thành công"
|
||||
}
|
||||
},
|
||||
"attendance": {
|
||||
@@ -750,6 +751,12 @@
|
||||
"disabled": "Tính năng bị vô hiệu hóa",
|
||||
"next": "Kế tiếp",
|
||||
"prev": "Trước đó",
|
||||
"version_history": {
|
||||
"title": "Lịch sử phiên bản",
|
||||
"fetch_more_versions": "Tìm nạp thêm phiên bản",
|
||||
"current_version": "Phiên bản hiện tại",
|
||||
"restore_version": "Khôi phục phiên bản này"
|
||||
},
|
||||
"drag": "kéo thẻ để sắp xếp lại",
|
||||
"no_lesson": "Chưa có bài học nào",
|
||||
"share_your_knowledge": "Chia sẻ kiến thức của bạn với thế giới bằng cách tạo ra những bài học hấp dẫn. Bắt đầu bằng cách nhấp vào nút Thêm."
|
||||
@@ -1363,4 +1370,4 @@
|
||||
"hyphen_rule": "Tên tổ chức không thể bắt đầu hoặc kết thúc bằng dấu gạch nối"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,9 +19,11 @@
|
||||
import PageBody from '$lib/components/PageBody/index.svelte';
|
||||
import Materials from '$lib/components/Course/components/Lesson/Materials/index.svelte';
|
||||
import Exercises from '$lib/components/Course/components/Lesson/Exercises/index.svelte';
|
||||
import LanguageLessonVersionHistory from '$lib/components/Course/components/Lesson/LanguageLessonVersionHistory.svelte';
|
||||
import MODES from '$lib/utils/constants/mode';
|
||||
import { course } from '$lib/components/Course/store';
|
||||
import Download from 'carbon-icons-svelte/lib/Download.svelte';
|
||||
import ResultOld from 'carbon-icons-svelte/lib/ResultOld.svelte';
|
||||
import OverflowMenuVertical from 'carbon-icons-svelte/lib/OverflowMenuVertical.svelte';
|
||||
import { apps } from '$lib/components/Apps/store';
|
||||
import APPS_CONSTANTS from '$lib/components/Apps/constants';
|
||||
@@ -39,7 +41,7 @@
|
||||
import { getIsLessonComplete } from '$lib/components/Course/components/Lesson/functions';
|
||||
import { t } from '$lib/utils/functions/translations';
|
||||
import { LANGUAGES } from '$lib/utils/constants/translation';
|
||||
import { ChevronLeft, ChevronRight } from 'carbon-icons-svelte';
|
||||
import { ChevronLeft, ChevronRight, Edit, Save } from 'carbon-icons-svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -50,6 +52,7 @@
|
||||
let isLoading = false;
|
||||
let isSaving = false;
|
||||
let isLessonComplete = false;
|
||||
let isVersionDrawerOpen = false;
|
||||
|
||||
function getLessonOrder(id: string) {
|
||||
const index = $lessons.findIndex((lesson) => lesson.id === id);
|
||||
@@ -268,6 +271,15 @@
|
||||
goto(path);
|
||||
};
|
||||
|
||||
const refetchDataAfterVersionRestore = () => {
|
||||
isVersionDrawerOpen = false;
|
||||
if (data.courseId && browser) {
|
||||
mode = MODES.view;
|
||||
fetchReqData(data.lessonId, data.isMaterialsTabActive);
|
||||
}
|
||||
snackbar.success('snackbar.lessons.success.version_restored');
|
||||
};
|
||||
|
||||
$: path = $page.url?.pathname?.replace(/\/exercises[\/ 0-9 a-z -]*/, '');
|
||||
|
||||
$: if (data.courseId && browser) {
|
||||
@@ -303,12 +315,16 @@
|
||||
]}
|
||||
>
|
||||
<svelte:fragment slot="widget">
|
||||
<div class="flex">
|
||||
<div class="flex items-center gap-1">
|
||||
<RoleBasedSecurity allowedRoles={[1, 2]}>
|
||||
{#if data.isMaterialsTabActive}
|
||||
<div class="mr-5">
|
||||
<Dropdown items={LANGUAGES} bind:selectedId={$lesson.locale} class="h-full" />
|
||||
</div>
|
||||
<!-- Version control -->
|
||||
{#if mode === MODES.edit && window.innerWidth >= 1024}
|
||||
<IconButton onClick={() => (isVersionDrawerOpen = true)}>
|
||||
<ResultOld size={24} />
|
||||
</IconButton>
|
||||
{/if}
|
||||
|
||||
<div class="tab">
|
||||
<IconButton onClick={toggleApps} buttonClassName="">
|
||||
<OverflowMenuVertical size={24} />
|
||||
@@ -321,19 +337,19 @@
|
||||
: 'hidden'
|
||||
} lg:flex items-center`}
|
||||
>
|
||||
<PrimaryButton
|
||||
className="mb-2 lg:mb-0 mr-2"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
$apps.dropdown = false;
|
||||
toggleMode();
|
||||
}}
|
||||
isDisabled={isSaving}
|
||||
disabled={isSaving}
|
||||
>
|
||||
{mode === MODES.edit
|
||||
? $t('course.navItem.lessons.done')
|
||||
: $t('course.navItem.lessons.edit')}
|
||||
</PrimaryButton>
|
||||
{#if mode === MODES.edit}
|
||||
<Save size={24} />
|
||||
{:else}
|
||||
<Edit size={24} />
|
||||
{/if}
|
||||
</IconButton>
|
||||
|
||||
{#if $course.metadata.lessonDownload && !!PUBLIC_SERVER_URL}
|
||||
<PrimaryButton
|
||||
@@ -346,6 +362,8 @@
|
||||
</PrimaryButton>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<Dropdown items={LANGUAGES} bind:selectedId={$lesson.locale} class="h-full" />
|
||||
{/if}
|
||||
</RoleBasedSecurity>
|
||||
</div>
|
||||
@@ -437,6 +455,15 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Version Control Preview -->
|
||||
{#if isVersionDrawerOpen && window.innerWidth >= 1024}
|
||||
<LanguageLessonVersionHistory
|
||||
open={isVersionDrawerOpen}
|
||||
on:close={() => (isVersionDrawerOpen = false)}
|
||||
on:restore={refetchDataAfterVersionRestore}
|
||||
/>
|
||||
{/if}
|
||||
</CourseContainer>
|
||||
|
||||
<style>
|
||||
|
||||
Generated
+8
@@ -334,6 +334,9 @@ importers:
|
||||
carbon-icons-svelte:
|
||||
specifier: ^12.1.0
|
||||
version: 12.1.0
|
||||
diff:
|
||||
specifier: ^5.2.0
|
||||
version: 5.2.0
|
||||
dotenv:
|
||||
specifier: ^16.3.1
|
||||
version: 16.3.1
|
||||
@@ -8886,6 +8889,11 @@ packages:
|
||||
engines: {node: '>=0.3.1'}
|
||||
dev: false
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
dev: true
|
||||
|
||||
/[email protected]:
|
||||
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||
dev: true
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
create or replace view "public"."lesson_versions" as SELECT llh.old_content,
|
||||
llh.new_content,
|
||||
llh."timestamp",
|
||||
ll.locale,
|
||||
ll.lesson_id
|
||||
FROM (lesson_language_history llh
|
||||
JOIN lesson_language ll ON ((ll.id = llh.lesson_language_id)));
|
||||
Reference in New Issue
Block a user