diff --git a/nextjs/README.md b/nextjs/README.md index 5a331b2..a36f2a2 100644 --- a/nextjs/README.md +++ b/nextjs/README.md @@ -2,4 +2,8 @@ - [ ] check out trpc - - - \ No newline at end of file + - + + + +lecture link prefectch false \ No newline at end of file diff --git a/nextjs/src/app/course/[courseName]/calendar/day/getStatus.tsx b/nextjs/src/app/course/[courseName]/calendar/day/getStatus.tsx index a60f0a1..01bbe3d 100644 --- a/nextjs/src/app/course/[courseName]/calendar/day/getStatus.tsx +++ b/nextjs/src/app/course/[courseName]/calendar/day/getStatus.tsx @@ -3,6 +3,7 @@ import { CanvasAssignment } from "@/models/canvas/assignments/canvasAssignment"; import { CanvasPage } from "@/models/canvas/pages/canvasPageModel"; import { CanvasQuiz } from "@/models/canvas/quizzes/canvasQuizModel"; import { LocalAssignment } from "@/models/local/assignment/localAssignment"; +import { LocalCourseSettings } from "@/models/local/localCourseSettings"; import { LocalCoursePage } from "@/models/local/page/localCoursePage"; import { LocalQuiz } from "@/models/local/quiz/localQuiz"; import { @@ -17,10 +18,12 @@ export const getStatus = ({ item, canvasItem, type, + settings, }: { item: LocalQuiz | LocalAssignment | LocalCoursePage; canvasItem?: CanvasQuiz | CanvasAssignment | CanvasPage; type: "assignment" | "page" | "quiz"; + settings: LocalCourseSettings; }): { status: "localOnly" | "incomplete" | "published"; message: ReactNode; @@ -102,7 +105,7 @@ export const getStatus = ({ }; const htmlIsSame = htmlIsCloseEnough( - markdownToHTMLSafe(assignment.description), + markdownToHTMLSafe(assignment.description, settings), canvasAssignment.description ); if (!htmlIsSame) diff --git a/nextjs/src/app/course/[courseName]/calendar/day/useTodaysItems.tsx b/nextjs/src/app/course/[courseName]/calendar/day/useTodaysItems.tsx index 05dd137..0f2c2a6 100644 --- a/nextjs/src/app/course/[courseName]/calendar/day/useTodaysItems.tsx +++ b/nextjs/src/app/course/[courseName]/calendar/day/useTodaysItems.tsx @@ -12,8 +12,10 @@ import { import { ReactNode } from "react"; import { useCalendarItemsContext } from "../../context/calendarItemsContext"; import { getStatus } from "./getStatus"; +import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; export function useTodaysItems(day: string) { + const [settings] = useLocalCourseSettingsQuery(); const dayAsDate = getDateFromStringOrThrow( day, "calculating same month in day items" @@ -43,6 +45,7 @@ export function useTodaysItems(day: string) { item: assignment, canvasItem: canvasAssignment, type: "assignment", + settings, }), }; }) @@ -65,6 +68,7 @@ export function useTodaysItems(day: string) { item: quiz, canvasItem: canvasQuiz, type: "quiz", + settings, }), }; }) @@ -87,6 +91,7 @@ export function useTodaysItems(day: string) { item: page, canvasItem: canvasPage, type: "page", + settings, }), }; }) diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/LecturePreview.tsx b/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/LecturePreview.tsx index 5e0d145..2ac908c 100644 --- a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/LecturePreview.tsx +++ b/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/LecturePreview.tsx @@ -1,7 +1,9 @@ +import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; import { Lecture } from "@/models/local/lecture"; import { markdownToHTMLSafe } from "@/services/htmlMarkdownUtils"; export default function LecturePreview({ lecture }: { lecture: Lecture }) { + const [settings] = useLocalCourseSettingsQuery(); return ( <>
@@ -12,7 +14,7 @@ export default function LecturePreview({ lecture }: { lecture: Lecture }) {
diff --git a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/preview/LecturePreviewPage.tsx b/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/preview/LecturePreviewPage.tsx index ba6298b..456ddd3 100644 --- a/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/preview/LecturePreviewPage.tsx +++ b/nextjs/src/app/course/[courseName]/lecture/[lectureDay]/preview/LecturePreviewPage.tsx @@ -16,7 +16,6 @@ export default function LecturePreviewPage({ const lecture = weeks .flatMap(({ lectures }) => lectures.map((lecture) => lecture)) .find((l) => l.date === lectureDay); - console.log(lecture); if (!lecture) { return
lecture not found for day
; diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentPreview.tsx b/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentPreview.tsx index a717963..9badab1 100644 --- a/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentPreview.tsx +++ b/nextjs/src/app/course/[courseName]/modules/[moduleName]/assignment/[assignmentName]/AssignmentPreview.tsx @@ -1,3 +1,4 @@ +import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; import { LocalAssignment } from "@/models/local/assignment/localAssignment"; import { rubricItemIsExtraCredit } from "@/models/local/assignment/rubricItem"; import { markdownToHTMLSafe } from "@/services/htmlMarkdownUtils"; @@ -8,6 +9,7 @@ export default function AssignmentPreview({ }: { assignment: LocalAssignment; }) { + const [settings] = useLocalCourseSettingsQuery(); const totalPoints = assignment.rubric.reduce( (sum, cur) => (rubricItemIsExtraCredit(cur) ? sum : sum + cur.points), 0 @@ -59,7 +61,7 @@ export default function AssignmentPreview({
diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/PagePreview.tsx b/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/PagePreview.tsx index 04f7c65..1f25a01 100644 --- a/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/PagePreview.tsx +++ b/nextjs/src/app/course/[courseName]/modules/[moduleName]/page/[pageName]/PagePreview.tsx @@ -1,13 +1,15 @@ +import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; import { LocalCoursePage } from "@/models/local/page/localCoursePage"; import { markdownToHTMLSafe } from "@/services/htmlMarkdownUtils"; import React from "react"; export default function PagePreview({ page }: { page: LocalCoursePage }) { + const [settings] = useLocalCourseSettingsQuery(); return (
); diff --git a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizPreview.tsx b/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizPreview.tsx index 22f3923..901aa84 100644 --- a/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizPreview.tsx +++ b/nextjs/src/app/course/[courseName]/modules/[moduleName]/quiz/[quizName]/QuizPreview.tsx @@ -1,4 +1,5 @@ import CheckIcon from "@/components/icons/CheckIcon"; +import { useLocalCourseSettingsQuery } from "@/hooks/localCourse/localCoursesHooks"; import { useQuizQuery } from "@/hooks/localCourse/quizHooks"; import { LocalQuizQuestion, @@ -14,6 +15,7 @@ export default function QuizPreview({ moduleName: string; }) { const [quiz] = useQuizQuery(moduleName, quizName); + const [settings] = useLocalCourseSettingsQuery(); return (
@@ -53,7 +55,7 @@ export default function QuizPreview({
@@ -75,6 +77,7 @@ export default function QuizPreview({ } function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) { + const [settings] = useLocalCourseSettingsQuery(); return (
@@ -86,7 +89,7 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
{question.questionType === QuestionType.MATCHING && (
@@ -128,7 +131,7 @@ function QuizQuestionPreview({ question }: { question: LocalQuizQuestion }) {
diff --git a/nextjs/src/app/newCourse/NewCourseForm.tsx b/nextjs/src/app/newCourse/NewCourseForm.tsx index afcc51a..ebe7305 100644 --- a/nextjs/src/app/newCourse/NewCourseForm.tsx +++ b/nextjs/src/app/newCourse/NewCourseForm.tsx @@ -104,6 +104,7 @@ export default function NewCourseForm() { return { ...groupWithoutCanvas, canvasId: undefined }; } ), + assets: [], } : { name: selectedDirectory, @@ -120,6 +121,7 @@ export default function NewCourseForm() { defaultFileUploadTypes: ["pdf", "png", "jpg", "jpeg"], defaultLockHoursOffset: 0, holidays: [], + assets: [], }; await createCourse.mutateAsync({ settings: newSettings, diff --git a/nextjs/src/hooks/canvas/canvasAssignmentHooks.ts b/nextjs/src/hooks/canvas/canvasAssignmentHooks.ts index 464ab07..2be8a90 100644 --- a/nextjs/src/hooks/canvas/canvasAssignmentHooks.ts +++ b/nextjs/src/hooks/canvas/canvasAssignmentHooks.ts @@ -22,6 +22,7 @@ export const useCanvasAssignmentsQuery = () => { }); }; + export const useAddAssignmentToCanvasMutation = () => { const [settings] = useLocalCourseSettingsQuery(); const { data: canvasModules } = useCanvasModulesQuery(); @@ -44,9 +45,11 @@ export const useAddAssignmentToCanvasMutation = () => { const assignmentGroup = settings.assignmentGroups.find( (g) => g.name === assignment.localAssignmentGroupName ); + const canvasAssignmentId = await canvasAssignmentService.create( settings.canvasId, assignment, + settings, assignmentGroup?.canvasId ); const canvasModule = canvasModules.find((c) => c.name === moduleName); @@ -89,6 +92,7 @@ export const useUpdateAssignmentInCanvasMutation = () => { settings.canvasId, canvasAssignmentId, assignment, + settings, assignmentGroup?.canvasId ); }, diff --git a/nextjs/src/hooks/canvas/canvasPageHooks.ts b/nextjs/src/hooks/canvas/canvasPageHooks.ts index d181679..86e653d 100644 --- a/nextjs/src/hooks/canvas/canvasPageHooks.ts +++ b/nextjs/src/hooks/canvas/canvasPageHooks.ts @@ -44,7 +44,7 @@ export const useCreateCanvasPageMutation = () => { } const canvasPage = await canvasPageService.create( settings.canvasId, - page + page,settings ); const canvasModule = canvasModules.find((c) => c.name === moduleName); @@ -78,7 +78,7 @@ export const useUpdateCanvasPageMutation = () => { }: { page: LocalCoursePage; canvasPageId: number; - }) => canvasPageService.update(settings.canvasId, canvasPageId, page), + }) => canvasPageService.update(settings.canvasId, canvasPageId, page, settings), onSuccess: () => { queryClient.invalidateQueries({ queryKey: canvasPageKeys.pagesInCourse(settings.canvasId), diff --git a/nextjs/src/hooks/canvas/canvasQuizHooks.ts b/nextjs/src/hooks/canvas/canvasQuizHooks.ts index cdfb68c..f13bc99 100644 --- a/nextjs/src/hooks/canvas/canvasQuizHooks.ts +++ b/nextjs/src/hooks/canvas/canvasQuizHooks.ts @@ -50,6 +50,7 @@ export const useAddQuizToCanvasMutation = () => { const canvasQuizId = await canvasQuizService.create( settings.canvasId, quiz, + settings, assignmentGroup?.canvasId ); diff --git a/nextjs/src/models/local/localCourseSettings.ts b/nextjs/src/models/local/localCourseSettings.ts index 1daccfe..4d7ea53 100644 --- a/nextjs/src/models/local/localCourseSettings.ts +++ b/nextjs/src/models/local/localCourseSettings.ts @@ -7,7 +7,6 @@ import { LocalAssignmentGroup, zodLocalAssignmentGroup, } from "./assignment/localAssignmentGroup"; -import { LocalModule } from "./localModules"; import { parse, stringify } from "yaml"; // export interface LocalCourse { @@ -59,6 +58,10 @@ export interface LocalCourseSettings { name: string; days: string[]; }[]; + assets: { + sourceUrl: string; + canvasUrl: string; + }[]; } export const zodLocalCourseSettings = z.object({ @@ -78,6 +81,12 @@ export const zodLocalCourseSettings = z.object({ days: z.string().array(), }) .array(), + assets: z + .object({ + sourceUrl: z.string(), + canvasUrl: z.string(), + }) + .array(), }); export function getDayOfWeek(date: Date): DayOfWeek { diff --git a/nextjs/src/services/canvas/canvasAssignmentService.ts b/nextjs/src/services/canvas/canvasAssignmentService.ts index 1d26e25..5b420f3 100644 --- a/nextjs/src/services/canvas/canvasAssignmentService.ts +++ b/nextjs/src/services/canvas/canvasAssignmentService.ts @@ -7,6 +7,7 @@ import { CanvasRubricCreationResponse } from "@/models/canvas/assignments/canvas import { assignmentPoints } from "@/models/local/assignment/utils/assignmentPointsUtils"; import { getDateFromString } from "@/models/local/utils/timeUtils"; import { getRubricCriterion } from "./canvasRubricUtils"; +import { LocalCourseSettings } from "@/models/local/localCourseSettings"; export const canvasAssignmentService = { async getAll(courseId: number): Promise { @@ -23,6 +24,7 @@ export const canvasAssignmentService = { async create( canvasCourseId: number, localAssignment: LocalAssignment, + settings: LocalCourseSettings, canvasAssignmentGroupId?: number ) { console.log(`Creating assignment: ${localAssignment.name}`); @@ -36,7 +38,7 @@ export const canvasAssignmentService = { allowed_extensions: localAssignment.allowedFileUploadExtensions.map( (e) => e.toString() ), - description: markdownToHTMLSafe(localAssignment.description), + description: markdownToHTMLSafe(localAssignment.description, settings), due_at: getDateFromString(localAssignment.dueAt)?.toISOString(), lock_at: localAssignment.lockAt && @@ -58,6 +60,7 @@ export const canvasAssignmentService = { courseId: number, canvasAssignmentId: number, localAssignment: LocalAssignment, + settings: LocalCourseSettings, canvasAssignmentGroupId?: number ) { console.log(`Updating assignment: ${localAssignment.name}`); @@ -71,7 +74,7 @@ export const canvasAssignmentService = { allowed_extensions: localAssignment.allowedFileUploadExtensions.map( (e) => e.toString() ), - description: markdownToHTMLSafe(localAssignment.description), + description: markdownToHTMLSafe(localAssignment.description, settings), due_at: getDateFromString(localAssignment.dueAt)?.toISOString(), lock_at: localAssignment.lockAt && diff --git a/nextjs/src/services/canvas/canvasPageService.ts b/nextjs/src/services/canvas/canvasPageService.ts index 6e912f4..e72a9b4 100644 --- a/nextjs/src/services/canvas/canvasPageService.ts +++ b/nextjs/src/services/canvas/canvasPageService.ts @@ -4,6 +4,7 @@ import { canvasApi, paginatedRequest } from "./canvasServiceUtils"; import { markdownToHTMLSafe } from "../htmlMarkdownUtils"; import { axiosClient } from "../axiosUtils"; import { rateLimitAwareDelete } from "./canvasWebRequestor"; +import { LocalCourseSettings } from "@/models/local/localCourseSettings"; export const canvasPageService = { async getAll(courseId: number): Promise { @@ -17,14 +18,15 @@ export const canvasPageService = { async create( canvasCourseId: number, - page: LocalCoursePage + page: LocalCoursePage, + settings: LocalCourseSettings ): Promise { console.log(`Creating course page: ${page.name}`); const url = `${canvasApi}/courses/${canvasCourseId}/pages`; const body = { wiki_page: { title: page.name, - body: markdownToHTMLSafe(page.text), + body: markdownToHTMLSafe(page.text, settings), }, }; @@ -38,14 +40,15 @@ export const canvasPageService = { async update( courseId: number, canvasPageId: number, - page: LocalCoursePage + page: LocalCoursePage, + settings: LocalCourseSettings ): Promise { console.log(`Updating course page: ${page.name}`); const url = `${canvasApi}/courses/${courseId}/pages/${canvasPageId}`; const body = { wiki_page: { title: page.name, - body: markdownToHTMLSafe(page.text), + body: markdownToHTMLSafe(page.text, settings), }, }; await axiosClient.put(url, body); diff --git a/nextjs/src/services/canvas/canvasQuizService.ts b/nextjs/src/services/canvas/canvasQuizService.ts index 942b22d..599dc5c 100644 --- a/nextjs/src/services/canvas/canvasQuizService.ts +++ b/nextjs/src/services/canvas/canvasQuizService.ts @@ -10,8 +10,12 @@ import { QuestionType, } from "@/models/local/quiz/localQuizQuestion"; import { CanvasQuizQuestion } from "@/models/canvas/quizzes/canvasQuizQuestionModel"; +import { LocalCourseSettings } from "@/models/local/localCourseSettings"; -const getAnswers = (question: LocalQuizQuestion) => { +const getAnswers = ( + question: LocalQuizQuestion, + settings: LocalCourseSettings +) => { if (question.questionType === QuestionType.MATCHING) return question.answers.map((a) => ({ answer_match_left: a.text, @@ -19,7 +23,7 @@ const getAnswers = (question: LocalQuizQuestion) => { })); return question.answers.map((answer) => ({ - answer_html: markdownToHTMLSafe(answer.text), + answer_html: markdownToHTMLSafe(answer.text, settings), answer_weight: answer.correct ? 100 : 0, })); }; @@ -28,18 +32,19 @@ const createQuestionOnly = async ( canvasCourseId: number, canvasQuizId: number, question: LocalQuizQuestion, - position: number + position: number, + settings: LocalCourseSettings ) => { console.log("Creating individual question"); //, question); const url = `${canvasApi}/courses/${canvasCourseId}/quizzes/${canvasQuizId}/questions`; const body = { question: { - question_text: markdownToHTMLSafe(question.text), + question_text: markdownToHTMLSafe(question.text, settings), question_type: `${question.questionType}_question`, points_possible: question.points, position, - answers: getAnswers(question), + answers: getAnswers(question, settings), }, }; @@ -93,13 +98,20 @@ const hackFixRedundantAssignments = async (canvasCourseId: number) => { const createQuizQuestions = async ( canvasCourseId: number, canvasQuizId: number, - localQuiz: LocalQuiz + localQuiz: LocalQuiz, + settings: LocalCourseSettings ) => { console.log("Creating quiz questions"); //, localQuiz); const tasks = localQuiz.questions.map( async (question, index) => - await createQuestionOnly(canvasCourseId, canvasQuizId, question, index) + await createQuestionOnly( + canvasCourseId, + canvasQuizId, + question, + index, + settings + ) ); const questionAndPositions = await Promise.all(tasks); await hackFixQuestionOrdering( @@ -126,15 +138,17 @@ export const canvasQuizService = { async create( canvasCourseId: number, localQuiz: LocalQuiz, + settings: LocalCourseSettings, canvasAssignmentGroupId?: number ) { console.log("Creating quiz", localQuiz); const url = `${canvasApi}/courses/${canvasCourseId}/quizzes`; + const body = { quiz: { title: localQuiz.name, - description: markdownToHTMLSafe(localQuiz.description), + description: markdownToHTMLSafe(localQuiz.description, settings), shuffle_answers: localQuiz.shuffleAnswers, access_code: localQuiz.password, show_correct_answers: localQuiz.showCorrectAnswers, @@ -158,7 +172,7 @@ export const canvasQuizService = { }; const { data: canvasQuiz } = await axiosClient.post(url, body); - await createQuizQuestions(canvasCourseId, canvasQuiz.id, localQuiz); + await createQuizQuestions(canvasCourseId, canvasQuiz.id, localQuiz, settings); return canvasQuiz.id; }, async delete(canvasCourseId: number, canvasQuizId: number) { diff --git a/nextjs/src/services/fileStorage/settingsFileStorageService.ts b/nextjs/src/services/fileStorage/settingsFileStorageService.ts index d0ed69a..3aa9812 100644 --- a/nextjs/src/services/fileStorage/settingsFileStorageService.ts +++ b/nextjs/src/services/fileStorage/settingsFileStorageService.ts @@ -50,6 +50,9 @@ const populateDefaultValues = (settingsFromFile: LocalCourseSettings) => { holidays: Array.isArray(settingsFromFile.holidays) ? settingsFromFile.holidays : [], + assets: Array.isArray(settingsFromFile.assets) + ? settingsFromFile.assets + : [], }; return settings; }; diff --git a/nextjs/src/services/htmlMarkdownUtils.ts b/nextjs/src/services/htmlMarkdownUtils.ts index 1e295d3..631d89d 100644 --- a/nextjs/src/services/htmlMarkdownUtils.ts +++ b/nextjs/src/services/htmlMarkdownUtils.ts @@ -1,18 +1,27 @@ "use client"; import { marked } from "marked"; -// import markedKatex from "marked-katex-extension"; import * as DOMPurify from "isomorphic-dompurify"; +import { LocalCourseSettings } from "@/models/local/localCourseSettings"; -export function markdownToHTMLSafe(markdownString: string) { - // const options = { - // throwOnError: false, - // nonStandard: true - // }; - - // marked.use(markedKatex(options)); +function extractImageSources(html: string) { + const parser = new DOMParser(); + const doc = parser.parseFromString(html, "text/html"); + const imgElements = doc.querySelectorAll("img"); + const srcUrls = Array.from(imgElements).map((img) => img.src); + return srcUrls; +} +function handleImages(html: string, settings: LocalCourseSettings) { + const imageSources = extractImageSources(html); + console.log(imageSources); +} +export function markdownToHTMLSafe( + markdownString: string, + settings: LocalCourseSettings +) { const clean = DOMPurify.sanitize( marked.parse(markdownString, { async: false, pedantic: false, gfm: true }) ); + handleImages(clean, settings); return clean; } diff --git a/nextjs/src/services/tests/fileStorage.test.ts b/nextjs/src/services/tests/fileStorage.test.ts index b007e3f..9d7f11b 100644 --- a/nextjs/src/services/tests/fileStorage.test.ts +++ b/nextjs/src/services/tests/fileStorage.test.ts @@ -31,6 +31,7 @@ describe("FileStorageTests", () => { defaultAssignmentSubmissionTypes: [], defaultFileUploadTypes: [], holidays: [], + assets: [] }; await fileStorageService.settings.updateCourseSettings(name, settings);