mirror of
https://github.com/alexmickelson/canvasManagement.git
synced 2026-03-25 15:18:32 -06:00
limited latex support
This commit is contained in:
54
nextjs/package-lock.json
generated
54
nextjs/package-lock.json
generated
@@ -12,7 +12,9 @@
|
||||
"@tanstack/react-query": "^5.54.1",
|
||||
"axios": "^1.7.5",
|
||||
"isomorphic-dompurify": "^2.15.0",
|
||||
"marked": "^14.1.0",
|
||||
"katex": "^0.16.11",
|
||||
"marked": "^14.1.2",
|
||||
"marked-katex-extension": "^5.1.2",
|
||||
"next": "^14.2.7",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
@@ -1751,6 +1753,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/katex": {
|
||||
"version": "0.16.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz",
|
||||
"integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.2.tgz",
|
||||
@@ -5196,6 +5204,31 @@
|
||||
"node": ">=4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/katex": {
|
||||
"version": "0.16.11",
|
||||
"resolved": "https://registry.npmjs.org/katex/-/katex-0.16.11.tgz",
|
||||
"integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==",
|
||||
"funding": [
|
||||
"https://opencollective.com/katex",
|
||||
"https://github.com/sponsors/katex"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"commander": "^8.3.0"
|
||||
},
|
||||
"bin": {
|
||||
"katex": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/katex/node_modules/commander": {
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz",
|
||||
"integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/keyv": {
|
||||
"version": "4.5.4",
|
||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||
@@ -5337,9 +5370,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "14.1.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-14.1.0.tgz",
|
||||
"integrity": "sha512-P93GikH/Pde0hM5TAXEd8I4JAYi8IB03n8qzW8Bh1BIEFpEyBoYxi/XWZA53LSpTeLBiMQOoSMj0u5E/tiVYTA==",
|
||||
"version": "14.1.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-14.1.2.tgz",
|
||||
"integrity": "sha512-f3r0yqpz31VXiDB/wj9GaOB0a2PRLQl6vJmXiFrniNwjkKdvakqJRULhjFKJpxOchlCRiG5fcacoUZY5Xa6PEQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
@@ -5348,6 +5381,19 @@
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/marked-katex-extension": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/marked-katex-extension/-/marked-katex-extension-5.1.2.tgz",
|
||||
"integrity": "sha512-jRtacvDAPULKBWArDno0IGpzzpUw12yb8OaEsv3dTlvcIr21+mF9kD+Bxo2m/ErX/2ZIml6zFVMnpxCpqx3stw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/katex": "^0.16.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"katex": ">=0.16 <0.17",
|
||||
"marked": ">=4 <15"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
"@tanstack/react-query": "^5.54.1",
|
||||
"axios": "^1.7.5",
|
||||
"isomorphic-dompurify": "^2.15.0",
|
||||
"marked": "^14.1.0",
|
||||
"katex": "^0.16.11",
|
||||
"marked": "^14.1.2",
|
||||
"marked-katex-extension": "^5.1.2",
|
||||
"next": "^14.2.7",
|
||||
"react": "^18",
|
||||
"react-dom": "^18",
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { axiosClient } from "@/services/axiosUtils";
|
||||
import { withErrorHandling } from "@/services/withErrorHandling";
|
||||
import { AxiosResponseHeaders, RawAxiosResponseHeaders } from "axios";
|
||||
import {
|
||||
AxiosResponseHeaders,
|
||||
isAxiosError,
|
||||
RawAxiosResponseHeaders,
|
||||
} from "axios";
|
||||
|
||||
const getUrl = (params: { rest: string[] }) => {
|
||||
const { rest } = params;
|
||||
@@ -27,7 +31,9 @@ const getNextUrl = (
|
||||
const nextLink = links.find((link) => link.includes('rel="next"'));
|
||||
|
||||
if (!nextLink) {
|
||||
console.log("could not find next url in link header, reached end of pagination");
|
||||
console.log(
|
||||
"could not find next url in link header, reached end of pagination"
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -58,9 +64,8 @@ export async function GET(
|
||||
url.toString()
|
||||
);
|
||||
|
||||
if(!Array.isArray(firstData))
|
||||
{
|
||||
return NextResponse.json(firstData)
|
||||
if (!Array.isArray(firstData)) {
|
||||
return NextResponse.json(firstData);
|
||||
}
|
||||
|
||||
var returnData = firstData;
|
||||
@@ -82,7 +87,6 @@ export async function GET(
|
||||
}
|
||||
|
||||
return NextResponse.json(returnData);
|
||||
|
||||
} catch (error: any) {
|
||||
return new NextResponse(
|
||||
JSON.stringify({ error: error.message || "Canvas GET request failed" }),
|
||||
@@ -97,18 +101,24 @@ export async function POST(
|
||||
{ params }: { params: { rest: string[] } }
|
||||
) {
|
||||
return withErrorHandling(async () => {
|
||||
const url = getUrl(params);
|
||||
const body = await req.json();
|
||||
let response;
|
||||
try {
|
||||
const url = getUrl(params);
|
||||
const body = await req.json();
|
||||
const response = await axiosClient.post(url.toString(), body);
|
||||
response = await axiosClient.post(url.toString(), body);
|
||||
|
||||
const headers = proxyResponseHeaders(response);
|
||||
return new NextResponse(JSON.stringify(response.data), { headers });
|
||||
} catch (error: any) {
|
||||
return new NextResponse(
|
||||
JSON.stringify({
|
||||
if (isAxiosError(error)) {
|
||||
console.log(url.toString(), body);
|
||||
console.log("response data", JSON.stringify( error.response?.data));
|
||||
console.log("is axios error");
|
||||
}
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error.message || "Canvas POST request failed",
|
||||
}),
|
||||
},
|
||||
{ status: error.response?.status || 500 }
|
||||
);
|
||||
}
|
||||
|
||||
@@ -135,7 +135,7 @@ function DraggableListItem({
|
||||
href={getModuleItemUrl(courseName, moduleName, type, item.name)}
|
||||
shallow={true}
|
||||
className={
|
||||
" border rounded-sm px-1 mx-1 break-all " +
|
||||
" border rounded-sm px-1 mx-1 break-all mb-1 " +
|
||||
" border-slate-600 bg-slate-800 " +
|
||||
" block "
|
||||
}
|
||||
|
||||
@@ -15,9 +15,11 @@ import {
|
||||
useAddAssignmentToCanvasMutation,
|
||||
useCanvasAssignmentsQuery,
|
||||
useDeleteAssignmentFromCanvasMutation,
|
||||
useUpdateAssignmentInCanvasMutation,
|
||||
} from "@/hooks/canvas/canvasAssignmentHooks";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
import { baseCanvasUrl } from "@/services/canvas/canvasServiceUtils";
|
||||
import ClientOnly from "@/components/ClientOnly";
|
||||
|
||||
export default function EditAssignment({
|
||||
moduleName,
|
||||
@@ -79,10 +81,12 @@ export default function EditAssignment({
|
||||
<AssignmentPreview assignment={assignment} />
|
||||
</div>
|
||||
</div>
|
||||
<AssignmentButtons
|
||||
moduleName={moduleName}
|
||||
assignmentName={assignmentName}
|
||||
/>
|
||||
<ClientOnly>
|
||||
<AssignmentButtons
|
||||
moduleName={moduleName}
|
||||
assignmentName={assignmentName}
|
||||
/>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -100,13 +104,16 @@ function AssignmentButtons({
|
||||
const { data: assignment } = useAssignmentQuery(moduleName, assignmentName);
|
||||
const addToCanvas = useAddAssignmentToCanvasMutation();
|
||||
const deleteFromCanvas = useDeleteAssignmentFromCanvasMutation();
|
||||
const updateAssignment = useUpdateAssignmentInCanvasMutation();
|
||||
|
||||
const assignmentInCanvas = canvasAssignments.find(
|
||||
(a) => a.name === assignmentName
|
||||
);
|
||||
return (
|
||||
<div className="p-5 flex flex-row justify-end gap-3">
|
||||
{(addToCanvas.isPending || deleteFromCanvas.isPending) && <Spinner />}
|
||||
{(addToCanvas.isPending ||
|
||||
deleteFromCanvas.isPending ||
|
||||
updateAssignment.isPending) && <Spinner />}
|
||||
{assignmentInCanvas && !assignmentInCanvas.published && (
|
||||
<div className="text-rose-300 my-auto">Not Published</div>
|
||||
)}
|
||||
@@ -115,7 +122,7 @@ function AssignmentButtons({
|
||||
disabled={addToCanvas.isPending}
|
||||
onClick={() => addToCanvas.mutate(assignment)}
|
||||
>
|
||||
Add to canvas....
|
||||
Add to canvas
|
||||
</button>
|
||||
)}
|
||||
{assignmentInCanvas && (
|
||||
@@ -127,6 +134,20 @@ function AssignmentButtons({
|
||||
View in Canvas
|
||||
</a>
|
||||
)}
|
||||
{assignmentInCanvas && (
|
||||
<button
|
||||
className=""
|
||||
disabled={deleteFromCanvas.isPending}
|
||||
onClick={() =>
|
||||
updateAssignment.mutate({
|
||||
canvasAssignmentId: assignmentInCanvas.id,
|
||||
assignment,
|
||||
})
|
||||
}
|
||||
>
|
||||
Update in Canvas
|
||||
</button>
|
||||
)}
|
||||
{assignmentInCanvas && (
|
||||
<button
|
||||
className="btn-danger"
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
useCanvasPagesQuery,
|
||||
useCreateCanvasPageMutation,
|
||||
} from "@/hooks/canvas/canvasPageHooks";
|
||||
import { Spinner } from "@/components/Spinner";
|
||||
import EditPageButtons from "./EditPageButtons";
|
||||
|
||||
export default function EditPage({
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
/* monaco editor */
|
||||
.monaco-editor-background,
|
||||
.monaco-editor .margin {
|
||||
background-color: #18181b !important;
|
||||
background-color: #020617 !important;
|
||||
/* background-color: #18181b !important; */
|
||||
}
|
||||
.monaco-editor { position: absolute !important; }
|
||||
|
||||
@@ -59,6 +60,19 @@ ol {
|
||||
list-style-type: decimal;
|
||||
padding-left: 1.5rem;
|
||||
}
|
||||
table {
|
||||
@apply border-collapse border border-gray-700;
|
||||
}
|
||||
|
||||
thead {
|
||||
@apply text-lg ;
|
||||
}
|
||||
|
||||
th, td {
|
||||
@apply px-2 py-1 border border-gray-700;
|
||||
}
|
||||
|
||||
|
||||
hr {
|
||||
@apply border-t border-gray-500 my-4;
|
||||
}
|
||||
@@ -70,6 +84,7 @@ blockquote {
|
||||
code {
|
||||
@apply font-mono text-sm bg-gray-800 px-2 py-1 rounded-md leading-tight inline-block;
|
||||
}
|
||||
|
||||
p {
|
||||
@apply mb-3;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import { Suspense } from "react";
|
||||
import { getQueryClient } from "./providersQueryClientUtils";
|
||||
import { hydrateCourses } from "@/hooks/hookHydration";
|
||||
import { dehydrate, HydrationBoundary } from "@tanstack/react-query";
|
||||
import { ToastBar, Toaster } from "react-hot-toast";
|
||||
import { MyToaster } from "./MyToaster";
|
||||
import "katex/dist/katex.min.css";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Canvas Manager 2.0",
|
||||
|
||||
@@ -46,13 +46,43 @@ export const useAddAssignmentToCanvasMutation = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async (assignmnet: LocalAssignment) => {
|
||||
mutationFn: async (assignment: LocalAssignment) => {
|
||||
const assignmentGroup = settings.assignmentGroups.find(
|
||||
(g) => g.name === assignmnet.localAssignmentGroupName
|
||||
(g) => g.name === assignment.localAssignmentGroupName
|
||||
);
|
||||
await canvasAssignmentService.create(
|
||||
settings.canvasId,
|
||||
assignmnet,
|
||||
assignment,
|
||||
assignmentGroup?.canvasId
|
||||
);
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: canvasAssignmentKeys.assignments(settings.canvasId),
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const useUpdateAssignmentInCanvasMutation = () => {
|
||||
const { data: settings } = useLocalCourseSettingsQuery();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
assignment,
|
||||
canvasAssignmentId,
|
||||
}: {
|
||||
assignment: LocalAssignment;
|
||||
canvasAssignmentId: number;
|
||||
}) => {
|
||||
const assignmentGroup = settings.assignmentGroups.find(
|
||||
(g) => g.name === assignment.localAssignmentGroupName
|
||||
);
|
||||
await canvasAssignmentService.update(
|
||||
settings.canvasId,
|
||||
canvasAssignmentId,
|
||||
assignment,
|
||||
assignmentGroup?.canvasId
|
||||
);
|
||||
},
|
||||
|
||||
@@ -31,20 +31,26 @@ axiosClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error: AxiosError) => {
|
||||
if (error.response) {
|
||||
console.log("response error", error.response);
|
||||
// console.log("response error", error.response);
|
||||
const responseErrorText =
|
||||
typeof error.response.data === "object"
|
||||
? (error.response.data as any).error
|
||||
: error.response.data;
|
||||
toast.error(
|
||||
`Error: ${error.response.status} - ${responseErrorText}, ${decodeURI(
|
||||
error.response.config.url ?? ""
|
||||
)}`
|
||||
);
|
||||
if (!isServer) {
|
||||
toast.error(
|
||||
`Error: ${error.response.status} - ${responseErrorText}, ${decodeURI(
|
||||
error.response.config.url ?? ""
|
||||
)}`
|
||||
);
|
||||
}
|
||||
} else if (error.request) {
|
||||
toast.error("Error: No response from server");
|
||||
if (!isServer) {
|
||||
toast.error("Error: No response from server");
|
||||
}
|
||||
} else {
|
||||
toast.error(`Error: ${error.message}`);
|
||||
if (!isServer) {
|
||||
toast.error(`Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import { axiosClient } from "../axiosUtils";
|
||||
import { markdownToHTMLSafe } from "../htmlMarkdownUtils";
|
||||
import { CanvasRubricCreationResponse } from "@/models/canvas/assignments/canvasRubricCreationResponse";
|
||||
import { assignmentPoints } from "@/models/local/assignment/utils/assignmentPointsUtils";
|
||||
import {
|
||||
getDateFromString,
|
||||
getDateFromStringOrThrow,
|
||||
} from "@/models/local/timeUtils";
|
||||
|
||||
const createRubric = async (
|
||||
courseId: number,
|
||||
@@ -77,18 +81,22 @@ export const canvasAssignmentService = {
|
||||
console.log(`Creating assignment: ${localAssignment.name}`);
|
||||
const url = `${canvasApi}/courses/${canvasCourseId}/assignments`;
|
||||
const body = {
|
||||
name: localAssignment.name,
|
||||
submission_types: localAssignment.submissionTypes.map((t) =>
|
||||
t.toString()
|
||||
),
|
||||
allowed_extensions: localAssignment.allowedFileUploadExtensions.map((e) =>
|
||||
e.toString()
|
||||
),
|
||||
description: markdownToHTMLSafe(localAssignment.description),
|
||||
due_at: localAssignment.dueAt,
|
||||
lock_at: localAssignment.lockAt,
|
||||
points_possible: assignmentPoints(localAssignment),
|
||||
assignment_group_id: canvasAssignmentGroupId,
|
||||
assignment: {
|
||||
name: localAssignment.name,
|
||||
submission_types: localAssignment.submissionTypes.map((t) =>
|
||||
t.toString()
|
||||
),
|
||||
allowed_extensions: localAssignment.allowedFileUploadExtensions.map(
|
||||
(e) => e.toString()
|
||||
),
|
||||
description: markdownToHTMLSafe(localAssignment.description),
|
||||
due_at: getDateFromString(localAssignment.dueAt)?.toISOString(),
|
||||
lock_at:
|
||||
localAssignment.lockAt &&
|
||||
getDateFromString(localAssignment.lockAt)?.toISOString(),
|
||||
points_possible: assignmentPoints(localAssignment),
|
||||
assignment_group_id: canvasAssignmentGroupId,
|
||||
},
|
||||
};
|
||||
|
||||
const response = await axiosClient.post<CanvasAssignment>(url, body);
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
"use client";
|
||||
import { marked } from "marked";
|
||||
import markedKatex from "marked-katex-extension";
|
||||
import * as DOMPurify from "isomorphic-dompurify";
|
||||
|
||||
export function markdownToHTMLSafe(markdownString: string) {
|
||||
const options = {
|
||||
throwOnError: false,
|
||||
nonStandard: true
|
||||
};
|
||||
|
||||
marked.use(markedKatex(options));
|
||||
|
||||
const clean = DOMPurify.sanitize(
|
||||
marked.parse(markdownString, { async: false, pedantic: false, gfm: true })
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user