canvaslms-cli 1.6.2 → 1.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +221 -195
- package/README.md +131 -129
- package/dist/commands/announcements.d.ts +2 -2
- package/dist/commands/announcements.d.ts.map +1 -1
- package/dist/commands/announcements.js +29 -20
- package/dist/commands/announcements.js.map +1 -1
- package/dist/commands/api.d.ts +1 -1
- package/dist/commands/api.d.ts.map +1 -1
- package/dist/commands/api.js +1 -1
- package/dist/commands/api.js.map +1 -1
- package/dist/commands/assignments.d.ts +2 -2
- package/dist/commands/assignments.d.ts.map +1 -1
- package/dist/commands/assignments.js +24 -19
- package/dist/commands/assignments.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +102 -85
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/grades.d.ts +2 -2
- package/dist/commands/grades.d.ts.map +1 -1
- package/dist/commands/grades.js +183 -228
- package/dist/commands/grades.js.map +1 -1
- package/dist/commands/list.d.ts +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +22 -20
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/profile.d.ts.map +1 -1
- package/dist/commands/profile.js +57 -32
- package/dist/commands/profile.js.map +1 -1
- package/dist/commands/submit.d.ts +1 -2
- package/dist/commands/submit.d.ts.map +1 -1
- package/dist/commands/submit.js +88 -79
- package/dist/commands/submit.js.map +1 -1
- package/dist/index.d.ts +14 -14
- package/dist/index.js +14 -14
- package/dist/lib/api-client.d.ts +3 -0
- package/dist/lib/api-client.d.ts.map +1 -1
- package/dist/lib/api-client.js +31 -16
- package/dist/lib/api-client.js.map +1 -1
- package/dist/lib/config-validator.d.ts.map +1 -1
- package/dist/lib/config-validator.js +12 -12
- package/dist/lib/config-validator.js.map +1 -1
- package/dist/lib/config.d.ts +1 -1
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +16 -14
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/display.d.ts +11 -5
- package/dist/lib/display.d.ts.map +1 -1
- package/dist/lib/display.js +261 -169
- package/dist/lib/display.js.map +1 -1
- package/dist/lib/file-upload.d.ts.map +1 -1
- package/dist/lib/file-upload.js +17 -17
- package/dist/lib/file-upload.js.map +1 -1
- package/dist/lib/interactive.d.ts +1 -1
- package/dist/lib/interactive.d.ts.map +1 -1
- package/dist/lib/interactive.js +194 -144
- package/dist/lib/interactive.js.map +1 -1
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +62 -63
- package/dist/src/index.js.map +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +10 -3
- package/dist/commands/coursenames.d.ts +0 -15
- package/dist/commands/coursenames.d.ts.map +0 -1
- package/dist/commands/coursenames.js +0 -150
- package/dist/commands/coursenames.js.map +0 -1
- package/dist/lib/course-utils.d.ts +0 -6
- package/dist/lib/course-utils.d.ts.map +0 -1
- package/dist/lib/course-utils.js +0 -17
- package/dist/lib/course-utils.js.map +0 -1
package/dist/lib/display.js
CHANGED
|
@@ -1,53 +1,60 @@
|
|
|
1
|
-
import chalk from
|
|
2
|
-
import { makeCanvasRequest } from
|
|
3
|
-
import { createReadlineInterface, askQuestion } from
|
|
4
|
-
export function pad(str, len, align =
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { makeCanvasRequest } from "./api-client.js";
|
|
3
|
+
import { createReadlineInterface, askQuestion } from "./interactive.js";
|
|
4
|
+
export function pad(str, len, align = "left") {
|
|
5
5
|
const strLen = stripAnsi(str).length;
|
|
6
6
|
const padding = Math.max(0, len - strLen);
|
|
7
|
-
if (align ===
|
|
8
|
-
return
|
|
7
|
+
if (align === "right") {
|
|
8
|
+
return " ".repeat(padding) + str;
|
|
9
9
|
}
|
|
10
|
-
else if (align ===
|
|
10
|
+
else if (align === "center") {
|
|
11
11
|
const leftPad = Math.floor(padding / 2);
|
|
12
12
|
const rightPad = padding - leftPad;
|
|
13
|
-
return
|
|
13
|
+
return " ".repeat(leftPad) + str + " ".repeat(rightPad);
|
|
14
14
|
}
|
|
15
|
-
return str +
|
|
15
|
+
return str + " ".repeat(padding);
|
|
16
16
|
}
|
|
17
17
|
function stripAnsi(str) {
|
|
18
|
-
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g,
|
|
18
|
+
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
19
19
|
}
|
|
20
20
|
export function truncate(str, maxLen) {
|
|
21
|
-
|
|
21
|
+
const visible = stripAnsi(str);
|
|
22
|
+
if (visible.length <= maxLen)
|
|
22
23
|
return str;
|
|
23
|
-
|
|
24
|
+
if (maxLen <= 3)
|
|
25
|
+
return visible.substring(0, maxLen);
|
|
26
|
+
const truncatedVisible = visible.substring(0, Math.max(0, maxLen - 3)) + "...";
|
|
27
|
+
return truncatedVisible;
|
|
24
28
|
}
|
|
25
29
|
function calculateColumnWidths(columns, data, terminalWidth, showRowNumbers) {
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const
|
|
30
|
+
const numCols = columns.length;
|
|
31
|
+
const borderOverhead = 2 * numCols + 1;
|
|
32
|
+
const rowNumWidth = showRowNumbers
|
|
33
|
+
? Math.max(3, String(data.length).length + 1)
|
|
34
|
+
: 0;
|
|
35
|
+
const rowNumOverhead = showRowNumbers ? 2 + rowNumWidth : 0;
|
|
36
|
+
const effectiveTermWidth = Math.min(terminalWidth, 240);
|
|
37
|
+
const availableWidth = Math.max(numCols * 4, effectiveTermWidth - borderOverhead - rowNumOverhead);
|
|
38
|
+
let widths = [];
|
|
32
39
|
const contentWidths = [];
|
|
33
40
|
let fixedWidthTotal = 0;
|
|
34
41
|
let totalFlex = 0;
|
|
35
42
|
columns.forEach((col, i) => {
|
|
36
|
-
const headerLen = col.header.length;
|
|
37
|
-
const maxContentLen = Math.max(headerLen, ...data.map(row => String(row[col.key]
|
|
43
|
+
const headerLen = stripAnsi(col.header).length;
|
|
44
|
+
const maxContentLen = Math.max(headerLen, ...data.map((row) => stripAnsi(String(row[col.key] ?? "")).length));
|
|
38
45
|
contentWidths[i] = maxContentLen;
|
|
39
46
|
if (col.width) {
|
|
40
47
|
widths[i] = col.width;
|
|
41
48
|
fixedWidthTotal += col.width;
|
|
42
49
|
}
|
|
43
50
|
else if (col.flex) {
|
|
44
|
-
const minW = col.minWidth ||
|
|
51
|
+
const minW = col.minWidth || 4;
|
|
45
52
|
widths[i] = minW;
|
|
46
53
|
fixedWidthTotal += minW;
|
|
47
54
|
totalFlex += col.flex;
|
|
48
55
|
}
|
|
49
56
|
else {
|
|
50
|
-
const minW = col.minWidth ||
|
|
57
|
+
const minW = col.minWidth || 4;
|
|
51
58
|
const maxW = col.maxWidth || maxContentLen;
|
|
52
59
|
widths[i] = Math.min(maxW, Math.max(minW, maxContentLen));
|
|
53
60
|
fixedWidthTotal += widths[i];
|
|
@@ -58,18 +65,25 @@ function calculateColumnWidths(columns, data, terminalWidth, showRowNumbers) {
|
|
|
58
65
|
columns.forEach((col, i) => {
|
|
59
66
|
if (col.flex) {
|
|
60
67
|
const extraWidth = Math.floor((remainingWidth * col.flex) / totalFlex);
|
|
61
|
-
const maxW = col.maxWidth ||
|
|
68
|
+
const maxW = col.maxWidth || 999;
|
|
62
69
|
widths[i] = Math.min((widths[i] ?? 0) + extraWidth, maxW);
|
|
63
70
|
}
|
|
64
71
|
});
|
|
65
72
|
}
|
|
66
73
|
let totalWidth = widths.reduce((sum, w) => sum + (w ?? 0), 0);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
74
|
+
while (totalWidth > availableWidth && totalWidth > numCols * 4) {
|
|
75
|
+
let maxIdx = 0;
|
|
76
|
+
let maxW = widths[0] ?? 0;
|
|
77
|
+
for (let i = 1; i < widths.length; i++) {
|
|
78
|
+
if ((widths[i] ?? 0) > maxW) {
|
|
79
|
+
maxW = widths[i] ?? 0;
|
|
80
|
+
maxIdx = i;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (maxW <= 4)
|
|
84
|
+
break;
|
|
85
|
+
widths[maxIdx] = Math.max(4, maxW - 1);
|
|
86
|
+
totalWidth--;
|
|
73
87
|
}
|
|
74
88
|
return widths;
|
|
75
89
|
}
|
|
@@ -78,13 +92,17 @@ export class Table {
|
|
|
78
92
|
data;
|
|
79
93
|
options;
|
|
80
94
|
rowNumWidth = 0;
|
|
95
|
+
lastLineCount = 0;
|
|
96
|
+
lastTableWidth = 0;
|
|
97
|
+
resizeHandler = null;
|
|
98
|
+
isWatching = false;
|
|
81
99
|
constructor(columns, options = {}) {
|
|
82
100
|
this.columns = columns;
|
|
83
101
|
this.data = [];
|
|
84
102
|
this.options = {
|
|
85
103
|
showRowNumbers: true,
|
|
86
|
-
rowNumberHeader:
|
|
87
|
-
...options
|
|
104
|
+
rowNumberHeader: "#",
|
|
105
|
+
...options,
|
|
88
106
|
};
|
|
89
107
|
}
|
|
90
108
|
addRow(row) {
|
|
@@ -100,8 +118,38 @@ export class Table {
|
|
|
100
118
|
}
|
|
101
119
|
renderWithResize() {
|
|
102
120
|
this.renderTable();
|
|
121
|
+
this.startWatching();
|
|
122
|
+
}
|
|
123
|
+
startWatching() {
|
|
124
|
+
if (this.isWatching || !process.stdout.isTTY)
|
|
125
|
+
return;
|
|
126
|
+
this.resizeHandler = () => {
|
|
127
|
+
this.clearTable();
|
|
128
|
+
this.renderTable();
|
|
129
|
+
};
|
|
130
|
+
process.stdout.on("resize", this.resizeHandler);
|
|
131
|
+
this.isWatching = true;
|
|
103
132
|
}
|
|
104
133
|
stopWatching() {
|
|
134
|
+
if (!this.isWatching || !this.resizeHandler)
|
|
135
|
+
return;
|
|
136
|
+
process.stdout.removeListener("resize", this.resizeHandler);
|
|
137
|
+
this.resizeHandler = null;
|
|
138
|
+
this.isWatching = false;
|
|
139
|
+
}
|
|
140
|
+
clearTable() {
|
|
141
|
+
if (this.lastLineCount <= 0)
|
|
142
|
+
return;
|
|
143
|
+
const maxPossibleWraps = Math.ceil(this.lastTableWidth / 20);
|
|
144
|
+
const worstCaseLines = this.lastLineCount * Math.max(maxPossibleWraps, 3);
|
|
145
|
+
const linesToClear = Math.max(worstCaseLines, 60);
|
|
146
|
+
process.stdout.write("\x1b[2K");
|
|
147
|
+
for (let i = 0; i < linesToClear; i++) {
|
|
148
|
+
process.stdout.write("\x1b[1A");
|
|
149
|
+
process.stdout.write("\x1b[2K");
|
|
150
|
+
}
|
|
151
|
+
process.stdout.write("\x1b[G");
|
|
152
|
+
process.stdout.write("\x1b[0J");
|
|
105
153
|
}
|
|
106
154
|
renderTable() {
|
|
107
155
|
const terminalWidth = process.stdout.columns || 100;
|
|
@@ -109,50 +157,78 @@ export class Table {
|
|
|
109
157
|
if (this.options.showRowNumbers) {
|
|
110
158
|
this.rowNumWidth = Math.max(3, String(this.data.length).length + 1);
|
|
111
159
|
}
|
|
160
|
+
const numCols = this.columns.length;
|
|
161
|
+
const borderWidth = 2 * numCols + 1;
|
|
162
|
+
const rowNumExtra = this.options.showRowNumbers ? 2 + this.rowNumWidth : 0;
|
|
163
|
+
const tableWidth = widths.reduce((a, b) => a + b, 0) + borderWidth + rowNumExtra;
|
|
164
|
+
this.lastTableWidth = tableWidth;
|
|
165
|
+
const linesPerRow = Math.max(1, Math.ceil(tableWidth / terminalWidth));
|
|
166
|
+
let lineCount = 0;
|
|
112
167
|
if (this.options.title) {
|
|
113
168
|
console.log(chalk.cyan.bold(`\n${this.options.title}`));
|
|
169
|
+
lineCount += 2;
|
|
114
170
|
}
|
|
115
171
|
this.renderTopBorder(widths);
|
|
172
|
+
lineCount += linesPerRow;
|
|
116
173
|
this.renderHeader(widths);
|
|
174
|
+
lineCount += linesPerRow;
|
|
117
175
|
this.renderHeaderSeparator(widths);
|
|
118
|
-
|
|
176
|
+
lineCount += linesPerRow;
|
|
177
|
+
this.data.forEach((row, index) => {
|
|
178
|
+
this.renderRow(row, index, widths);
|
|
179
|
+
lineCount += linesPerRow;
|
|
180
|
+
});
|
|
119
181
|
this.renderBottomBorder(widths);
|
|
182
|
+
lineCount += linesPerRow;
|
|
183
|
+
this.lastLineCount = lineCount;
|
|
120
184
|
}
|
|
121
185
|
renderTopBorder(widths) {
|
|
122
|
-
let border = chalk.gray(
|
|
186
|
+
let border = chalk.gray("╭─");
|
|
123
187
|
if (this.options.showRowNumbers) {
|
|
124
|
-
border += chalk.gray(
|
|
188
|
+
border += chalk.gray("─".repeat(this.rowNumWidth)) + chalk.gray("┬─");
|
|
125
189
|
}
|
|
126
|
-
border += widths
|
|
127
|
-
|
|
190
|
+
border += widths
|
|
191
|
+
.map((w) => chalk.gray("─".repeat(w)))
|
|
192
|
+
.join(chalk.gray("┬─"));
|
|
193
|
+
border += chalk.gray("╮");
|
|
128
194
|
console.log(border);
|
|
129
195
|
}
|
|
130
196
|
renderHeader(widths) {
|
|
131
|
-
let header = chalk.gray(
|
|
197
|
+
let header = chalk.gray("│ ");
|
|
132
198
|
if (this.options.showRowNumbers) {
|
|
133
|
-
header +=
|
|
199
|
+
header +=
|
|
200
|
+
chalk.cyan.bold(pad(truncate(this.options.rowNumberHeader, this.rowNumWidth), this.rowNumWidth)) + chalk.gray("│ ");
|
|
134
201
|
}
|
|
135
|
-
header += this.columns
|
|
136
|
-
|
|
202
|
+
header += this.columns
|
|
203
|
+
.map((col, i) => {
|
|
204
|
+
const colWidth = widths[i] || 10;
|
|
205
|
+
return chalk.cyan.bold(pad(truncate(col.header, colWidth), colWidth, col.align));
|
|
206
|
+
})
|
|
207
|
+
.join(chalk.gray("│ "));
|
|
208
|
+
header += chalk.gray("│");
|
|
137
209
|
console.log(header);
|
|
138
210
|
}
|
|
139
211
|
renderHeaderSeparator(widths) {
|
|
140
|
-
let separator = chalk.gray(
|
|
212
|
+
let separator = chalk.gray("├─");
|
|
141
213
|
if (this.options.showRowNumbers) {
|
|
142
|
-
separator += chalk.gray(
|
|
214
|
+
separator += chalk.gray("─".repeat(this.rowNumWidth)) + chalk.gray("┼─");
|
|
143
215
|
}
|
|
144
|
-
separator += widths
|
|
145
|
-
|
|
216
|
+
separator += widths
|
|
217
|
+
.map((w) => chalk.gray("─".repeat(w)))
|
|
218
|
+
.join(chalk.gray("┼─"));
|
|
219
|
+
separator += chalk.gray("┤");
|
|
146
220
|
console.log(separator);
|
|
147
221
|
}
|
|
148
222
|
renderRow(row, index, widths) {
|
|
149
|
-
let rowStr = chalk.gray(
|
|
223
|
+
let rowStr = chalk.gray("│ ");
|
|
150
224
|
if (this.options.showRowNumbers) {
|
|
151
|
-
rowStr +=
|
|
225
|
+
rowStr +=
|
|
226
|
+
chalk.white(pad(`${index + 1}.`, this.rowNumWidth)) + chalk.gray("│ ");
|
|
152
227
|
}
|
|
153
|
-
rowStr += this.columns
|
|
228
|
+
rowStr += this.columns
|
|
229
|
+
.map((col, i) => {
|
|
154
230
|
const colWidth = widths[i] || 10;
|
|
155
|
-
let value = String(row[col.key] ??
|
|
231
|
+
let value = String(row[col.key] ?? "");
|
|
156
232
|
value = truncate(value, colWidth);
|
|
157
233
|
value = pad(value, colWidth, col.align);
|
|
158
234
|
if (col.color) {
|
|
@@ -162,70 +238,72 @@ export class Table {
|
|
|
162
238
|
value = chalk.white(value);
|
|
163
239
|
}
|
|
164
240
|
return value;
|
|
165
|
-
})
|
|
166
|
-
|
|
241
|
+
})
|
|
242
|
+
.join(chalk.gray("│ "));
|
|
243
|
+
rowStr += chalk.gray("│");
|
|
167
244
|
console.log(rowStr);
|
|
168
245
|
}
|
|
169
246
|
renderBottomBorder(widths) {
|
|
170
|
-
let border = chalk.gray(
|
|
247
|
+
let border = chalk.gray("╰─");
|
|
171
248
|
if (this.options.showRowNumbers) {
|
|
172
|
-
border += chalk.gray(
|
|
249
|
+
border += chalk.gray("─".repeat(this.rowNumWidth)) + chalk.gray("┴─");
|
|
173
250
|
}
|
|
174
|
-
border += widths
|
|
175
|
-
|
|
251
|
+
border += widths
|
|
252
|
+
.map((w) => chalk.gray("─".repeat(w)))
|
|
253
|
+
.join(chalk.gray("┴─"));
|
|
254
|
+
border += chalk.gray("╯");
|
|
176
255
|
console.log(border);
|
|
177
256
|
}
|
|
178
257
|
}
|
|
179
258
|
export function displayCourses(courses, options = {}) {
|
|
180
259
|
const columns = [
|
|
181
|
-
{ key:
|
|
260
|
+
{ key: "name", header: "Course Name", flex: 1, minWidth: 15 },
|
|
182
261
|
];
|
|
183
262
|
if (options.showId) {
|
|
184
|
-
columns.push({ key:
|
|
263
|
+
columns.push({ key: "id", header: "ID", minWidth: 5, maxWidth: 10 });
|
|
185
264
|
}
|
|
186
265
|
const table = new Table(columns);
|
|
187
|
-
courses.forEach(course => {
|
|
266
|
+
courses.forEach((course) => {
|
|
188
267
|
table.addRow({ name: course.name, id: course.id });
|
|
189
268
|
});
|
|
190
269
|
table.render();
|
|
191
270
|
}
|
|
192
271
|
export async function pickCourse(options = {}) {
|
|
193
272
|
const rl = createReadlineInterface();
|
|
194
|
-
console.log(chalk.cyan.bold(options.title ||
|
|
195
|
-
const queryParams = [
|
|
196
|
-
|
|
197
|
-
'include[]=favorites'
|
|
198
|
-
];
|
|
199
|
-
const courses = await makeCanvasRequest('get', 'courses', queryParams);
|
|
273
|
+
console.log(chalk.cyan.bold(options.title || "\nLoading your courses, please wait..."));
|
|
274
|
+
const queryParams = ["enrollment_state=active", "include[]=favorites"];
|
|
275
|
+
const courses = await makeCanvasRequest("get", "courses", queryParams);
|
|
200
276
|
if (!courses || courses.length === 0) {
|
|
201
|
-
console.log(chalk.red(
|
|
277
|
+
console.log(chalk.red("Error: No courses found."));
|
|
202
278
|
rl.close();
|
|
203
279
|
return null;
|
|
204
280
|
}
|
|
205
281
|
let displayCourses = courses;
|
|
206
282
|
if (!options.showAll) {
|
|
207
|
-
const starred = courses.filter(c => c.is_favorite);
|
|
283
|
+
const starred = courses.filter((c) => c.is_favorite);
|
|
208
284
|
if (starred.length > 0) {
|
|
209
285
|
displayCourses = starred;
|
|
210
286
|
}
|
|
211
287
|
}
|
|
212
288
|
console.log(chalk.green(`✓ Found ${displayCourses.length} course(s).`));
|
|
213
289
|
const table = new Table([
|
|
214
|
-
{ key:
|
|
290
|
+
{ key: "name", header: "Course Name", flex: 1, minWidth: 15 },
|
|
215
291
|
]);
|
|
216
|
-
displayCourses.forEach(course => table.addRow({ name: course.name }));
|
|
292
|
+
displayCourses.forEach((course) => table.addRow({ name: course.name }));
|
|
217
293
|
table.renderWithResize();
|
|
218
|
-
const prompt = options.prompt || chalk.bold.cyan(
|
|
294
|
+
const prompt = options.prompt || chalk.bold.cyan("\nEnter course number: ");
|
|
219
295
|
const courseChoice = await askQuestion(rl, prompt);
|
|
220
296
|
table.stopWatching();
|
|
221
|
-
if (!courseChoice.trim() ||
|
|
222
|
-
|
|
297
|
+
if (!courseChoice.trim() ||
|
|
298
|
+
courseChoice === ".." ||
|
|
299
|
+
courseChoice.toLowerCase() === "back") {
|
|
300
|
+
console.log(chalk.red("No course selected. Exiting..."));
|
|
223
301
|
rl.close();
|
|
224
302
|
return null;
|
|
225
303
|
}
|
|
226
304
|
const courseIndex = parseInt(courseChoice) - 1;
|
|
227
305
|
if (courseIndex < 0 || courseIndex >= displayCourses.length) {
|
|
228
|
-
console.log(chalk.red(
|
|
306
|
+
console.log(chalk.red("Invalid course selection."));
|
|
229
307
|
rl.close();
|
|
230
308
|
return null;
|
|
231
309
|
}
|
|
@@ -234,8 +312,12 @@ export async function pickCourse(options = {}) {
|
|
|
234
312
|
return { course: selectedCourse, rl };
|
|
235
313
|
}
|
|
236
314
|
export function formatGrade(submission, pointsPossible) {
|
|
237
|
-
if (submission &&
|
|
238
|
-
|
|
315
|
+
if (submission &&
|
|
316
|
+
submission.score !== null &&
|
|
317
|
+
submission.score !== undefined) {
|
|
318
|
+
const score = submission.score % 1 === 0
|
|
319
|
+
? Math.round(submission.score)
|
|
320
|
+
: submission.score;
|
|
239
321
|
const text = `${score}/${pointsPossible}`;
|
|
240
322
|
const percentage = pointsPossible > 0 ? (score / pointsPossible) * 100 : 0;
|
|
241
323
|
let color = chalk.white;
|
|
@@ -248,133 +330,141 @@ export function formatGrade(submission, pointsPossible) {
|
|
|
248
330
|
return { text, color };
|
|
249
331
|
}
|
|
250
332
|
else if (submission && submission.excused) {
|
|
251
|
-
return { text:
|
|
333
|
+
return { text: "Excused", color: chalk.blue };
|
|
252
334
|
}
|
|
253
335
|
else if (submission && submission.missing) {
|
|
254
|
-
return { text:
|
|
336
|
+
return { text: "Missing", color: chalk.red };
|
|
255
337
|
}
|
|
256
338
|
else if (pointsPossible) {
|
|
257
339
|
return { text: `–/${pointsPossible}`, color: chalk.gray };
|
|
258
340
|
}
|
|
259
|
-
return { text:
|
|
341
|
+
return { text: "N/A", color: chalk.gray };
|
|
260
342
|
}
|
|
261
343
|
export function formatDueDate(dueAt) {
|
|
262
344
|
if (!dueAt)
|
|
263
|
-
return
|
|
345
|
+
return "No due date";
|
|
264
346
|
return new Date(dueAt).toLocaleString();
|
|
265
347
|
}
|
|
266
348
|
export function displayAssignments(assignments, options = {}) {
|
|
267
349
|
const columns = [
|
|
268
|
-
{ key:
|
|
350
|
+
{ key: "name", header: "Assignment Name", flex: 1, minWidth: 15 },
|
|
269
351
|
];
|
|
270
352
|
if (options.showId) {
|
|
271
|
-
columns.push({ key:
|
|
353
|
+
columns.push({ key: "id", header: "ID", width: 7 });
|
|
272
354
|
}
|
|
273
355
|
if (options.showGrade) {
|
|
274
356
|
columns.push({
|
|
275
|
-
key:
|
|
276
|
-
header:
|
|
277
|
-
|
|
278
|
-
maxWidth: 12,
|
|
357
|
+
key: "grade",
|
|
358
|
+
header: "Grade",
|
|
359
|
+
width: 10,
|
|
279
360
|
color: (value, row) => {
|
|
280
361
|
const gradeInfo = formatGrade(row._submission, row._pointsPossible);
|
|
281
362
|
return gradeInfo.color(value);
|
|
282
|
-
}
|
|
363
|
+
},
|
|
283
364
|
});
|
|
284
365
|
}
|
|
285
366
|
if (options.showDueDate) {
|
|
286
|
-
columns.push({ key:
|
|
367
|
+
columns.push({ key: "dueDate", header: "Due", width: 10 });
|
|
287
368
|
}
|
|
288
369
|
if (options.showStatus) {
|
|
289
370
|
columns.push({
|
|
290
|
-
key:
|
|
291
|
-
header:
|
|
292
|
-
|
|
293
|
-
maxWidth: 14,
|
|
371
|
+
key: "status",
|
|
372
|
+
header: "Status",
|
|
373
|
+
width: 10,
|
|
294
374
|
color: (value, row) => {
|
|
295
375
|
return row._isSubmitted ? chalk.green(value) : chalk.yellow(value);
|
|
296
|
-
}
|
|
376
|
+
},
|
|
297
377
|
});
|
|
298
378
|
}
|
|
299
379
|
const table = new Table(columns);
|
|
300
|
-
assignments.forEach(assignment => {
|
|
380
|
+
assignments.forEach((assignment) => {
|
|
301
381
|
const submission = assignment.submission;
|
|
302
382
|
const gradeInfo = formatGrade(submission, assignment.points_possible || 0);
|
|
303
383
|
const isSubmitted = !!(submission && submission.submitted_at);
|
|
384
|
+
let dueDate = "No due";
|
|
385
|
+
if (assignment.due_at) {
|
|
386
|
+
const d = new Date(assignment.due_at);
|
|
387
|
+
dueDate = `${(d.getMonth() + 1).toString().padStart(2, "0")}/${d.getDate().toString().padStart(2, "0")}/${d.getFullYear()}`;
|
|
388
|
+
}
|
|
304
389
|
table.addRow({
|
|
305
390
|
name: assignment.name,
|
|
306
391
|
id: assignment.id,
|
|
307
392
|
grade: gradeInfo.text,
|
|
308
|
-
dueDate:
|
|
309
|
-
status: isSubmitted ?
|
|
393
|
+
dueDate: dueDate,
|
|
394
|
+
status: isSubmitted ? "✓ Submitted" : "Not submit",
|
|
310
395
|
_submission: submission,
|
|
311
396
|
_pointsPossible: assignment.points_possible || 0,
|
|
312
|
-
_isSubmitted: isSubmitted
|
|
397
|
+
_isSubmitted: isSubmitted,
|
|
313
398
|
});
|
|
314
399
|
});
|
|
315
|
-
table.
|
|
400
|
+
table.renderWithResize();
|
|
401
|
+
return table;
|
|
316
402
|
}
|
|
317
403
|
function getAssignmentType(assignment) {
|
|
318
|
-
if (!assignment.submission_types ||
|
|
319
|
-
|
|
404
|
+
if (!assignment.submission_types ||
|
|
405
|
+
assignment.submission_types.length === 0) {
|
|
406
|
+
return "Unknown";
|
|
320
407
|
}
|
|
321
408
|
const types = assignment.submission_types;
|
|
322
|
-
if (types.includes(
|
|
323
|
-
return
|
|
409
|
+
if (types.includes("online_quiz")) {
|
|
410
|
+
return "Quiz";
|
|
324
411
|
}
|
|
325
|
-
else if (types.includes(
|
|
326
|
-
if (assignment.allowed_extensions &&
|
|
412
|
+
else if (types.includes("online_upload")) {
|
|
413
|
+
if (assignment.allowed_extensions &&
|
|
414
|
+
assignment.allowed_extensions.length > 0) {
|
|
327
415
|
const exts = assignment.allowed_extensions.slice(0, 3);
|
|
328
|
-
const extList = exts.join(
|
|
329
|
-
const suffix = assignment.allowed_extensions.length > 3 ?
|
|
416
|
+
const extList = exts.join(", ");
|
|
417
|
+
const suffix = assignment.allowed_extensions.length > 3 ? "..." : "";
|
|
330
418
|
return `${extList}${suffix}`;
|
|
331
419
|
}
|
|
332
|
-
return
|
|
420
|
+
return "Any file";
|
|
333
421
|
}
|
|
334
|
-
else if (types.includes(
|
|
335
|
-
return
|
|
422
|
+
else if (types.includes("online_text_entry")) {
|
|
423
|
+
return "Text Entry";
|
|
336
424
|
}
|
|
337
|
-
else if (types.includes(
|
|
338
|
-
return
|
|
425
|
+
else if (types.includes("online_url")) {
|
|
426
|
+
return "URL";
|
|
339
427
|
}
|
|
340
|
-
else if (types.includes(
|
|
341
|
-
return
|
|
428
|
+
else if (types.includes("external_tool")) {
|
|
429
|
+
return "External Tool";
|
|
342
430
|
}
|
|
343
|
-
else if (types.includes(
|
|
344
|
-
return
|
|
431
|
+
else if (types.includes("media_recording")) {
|
|
432
|
+
return "Media";
|
|
345
433
|
}
|
|
346
434
|
else if (types[0]) {
|
|
347
|
-
return types[0].replace(/_/g,
|
|
435
|
+
return types[0].replace(/_/g, " ");
|
|
348
436
|
}
|
|
349
|
-
return
|
|
437
|
+
return "Unknown";
|
|
350
438
|
}
|
|
351
439
|
export function displaySubmitAssignments(assignments) {
|
|
352
440
|
const columns = [
|
|
353
|
-
{ key:
|
|
354
|
-
{ key:
|
|
355
|
-
{ key:
|
|
441
|
+
{ key: "name", header: "Assignment Name", flex: 1, minWidth: 15 },
|
|
442
|
+
{ key: "type", header: "Type", width: 8 },
|
|
443
|
+
{ key: "dueDate", header: "Due", width: 10 },
|
|
356
444
|
{
|
|
357
|
-
key:
|
|
358
|
-
header:
|
|
359
|
-
|
|
360
|
-
maxWidth: 14,
|
|
445
|
+
key: "status",
|
|
446
|
+
header: "Status",
|
|
447
|
+
width: 10,
|
|
361
448
|
color: (value, row) => {
|
|
362
449
|
return row._isSubmitted ? chalk.green(value) : chalk.yellow(value);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
450
|
+
},
|
|
451
|
+
},
|
|
365
452
|
];
|
|
366
453
|
const table = new Table(columns);
|
|
367
|
-
assignments.forEach(assignment => {
|
|
454
|
+
assignments.forEach((assignment) => {
|
|
368
455
|
const submission = assignment.submission;
|
|
369
456
|
const isSubmitted = !!(submission && submission.submitted_at);
|
|
457
|
+
let dueDate = "No due date";
|
|
458
|
+
if (assignment.due_at) {
|
|
459
|
+
const d = new Date(assignment.due_at);
|
|
460
|
+
dueDate = `${(d.getMonth() + 1).toString().padStart(2, "0")}/${d.getDate().toString().padStart(2, "0")}/${d.getFullYear()}`;
|
|
461
|
+
}
|
|
370
462
|
table.addRow({
|
|
371
463
|
name: assignment.name,
|
|
372
464
|
type: getAssignmentType(assignment),
|
|
373
|
-
dueDate:
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
status: isSubmitted ? '✓ Submitted' : 'Not submitted',
|
|
377
|
-
_isSubmitted: isSubmitted
|
|
465
|
+
dueDate: dueDate,
|
|
466
|
+
status: isSubmitted ? "✓ Submitted" : "Not submit",
|
|
467
|
+
_isSubmitted: isSubmitted,
|
|
378
468
|
});
|
|
379
469
|
});
|
|
380
470
|
table.renderWithResize();
|
|
@@ -382,30 +472,30 @@ export function displaySubmitAssignments(assignments) {
|
|
|
382
472
|
}
|
|
383
473
|
export function displayAnnouncements(announcements) {
|
|
384
474
|
const columns = [
|
|
385
|
-
{ key:
|
|
386
|
-
{ key:
|
|
475
|
+
{ key: "title", header: "Title", flex: 1, minWidth: 15 },
|
|
476
|
+
{ key: "posted", header: "Posted", width: 10 },
|
|
387
477
|
];
|
|
388
478
|
const table = new Table(columns);
|
|
389
|
-
announcements.forEach(announcement => {
|
|
479
|
+
announcements.forEach((announcement) => {
|
|
390
480
|
const date = announcement.posted_at
|
|
391
481
|
? new Date(announcement.posted_at).toLocaleDateString()
|
|
392
|
-
:
|
|
482
|
+
: "N/A";
|
|
393
483
|
table.addRow({
|
|
394
|
-
title: announcement.title ||
|
|
395
|
-
posted: date
|
|
484
|
+
title: announcement.title || "Untitled",
|
|
485
|
+
posted: date,
|
|
396
486
|
});
|
|
397
487
|
});
|
|
398
488
|
table.renderWithResize();
|
|
399
489
|
return table;
|
|
400
490
|
}
|
|
401
|
-
export function printSeparator(char =
|
|
402
|
-
const width = length ||
|
|
491
|
+
export function printSeparator(char = "─", length) {
|
|
492
|
+
const width = length || process.stdout.columns || 60;
|
|
403
493
|
console.log(chalk.cyan.bold(char.repeat(width)));
|
|
404
494
|
}
|
|
405
495
|
export function printHeader(title) {
|
|
406
|
-
console.log(chalk.cyan.bold(
|
|
496
|
+
console.log(chalk.cyan.bold("\n" + "─".repeat(60)));
|
|
407
497
|
console.log(chalk.cyan.bold(title));
|
|
408
|
-
console.log(chalk.cyan.bold(
|
|
498
|
+
console.log(chalk.cyan.bold("─".repeat(60)));
|
|
409
499
|
}
|
|
410
500
|
export function printSuccess(message) {
|
|
411
501
|
console.log(chalk.green(`✓ ${message}`));
|
|
@@ -420,40 +510,40 @@ export function printWarning(message) {
|
|
|
420
510
|
console.log(chalk.yellow(message));
|
|
421
511
|
}
|
|
422
512
|
function cleanHtmlContent(html) {
|
|
423
|
-
return html
|
|
424
|
-
.replace(/ /g,
|
|
425
|
-
.replace(/&/g,
|
|
426
|
-
.replace(/</g,
|
|
427
|
-
.replace(/>/g,
|
|
513
|
+
return (html
|
|
514
|
+
.replace(/ /g, " ")
|
|
515
|
+
.replace(/&/g, "&")
|
|
516
|
+
.replace(/</g, "<")
|
|
517
|
+
.replace(/>/g, ">")
|
|
428
518
|
.replace(/"/g, '"')
|
|
429
519
|
.replace(/'/g, "'")
|
|
430
520
|
.replace(/'/g, "'")
|
|
431
|
-
.replace(/<br\s*\/?>/gi,
|
|
432
|
-
.replace(/<\/(p|div|li|h[1-6])>/gi,
|
|
433
|
-
.replace(/<li[^>]*>/gi,
|
|
434
|
-
.replace(/<[^>]+>/g,
|
|
435
|
-
.replace(/\n{3,}/g,
|
|
436
|
-
.replace(/[ \t]+/g,
|
|
437
|
-
.split(
|
|
438
|
-
.map(line => line.trim())
|
|
439
|
-
.join(
|
|
440
|
-
.trim();
|
|
521
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
522
|
+
.replace(/<\/(p|div|li|h[1-6])>/gi, "\n")
|
|
523
|
+
.replace(/<li[^>]*>/gi, "• ")
|
|
524
|
+
.replace(/<[^>]+>/g, "")
|
|
525
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
526
|
+
.replace(/[ \t]+/g, " ")
|
|
527
|
+
.split("\n")
|
|
528
|
+
.map((line) => line.trim())
|
|
529
|
+
.join("\n")
|
|
530
|
+
.trim());
|
|
441
531
|
}
|
|
442
532
|
function wordWrap(text, maxWidth) {
|
|
443
533
|
const lines = [];
|
|
444
|
-
const paragraphs = text.split(
|
|
534
|
+
const paragraphs = text.split("\n");
|
|
445
535
|
for (const paragraph of paragraphs) {
|
|
446
|
-
if (paragraph.trim() ===
|
|
447
|
-
lines.push(
|
|
536
|
+
if (paragraph.trim() === "") {
|
|
537
|
+
lines.push("");
|
|
448
538
|
continue;
|
|
449
539
|
}
|
|
450
|
-
const words = paragraph.split(
|
|
451
|
-
let currentLine =
|
|
540
|
+
const words = paragraph.split(" ").filter((w) => w.length > 0);
|
|
541
|
+
let currentLine = "";
|
|
452
542
|
for (const word of words) {
|
|
453
543
|
if (word.length > maxWidth) {
|
|
454
544
|
if (currentLine) {
|
|
455
545
|
lines.push(currentLine);
|
|
456
|
-
currentLine =
|
|
546
|
+
currentLine = "";
|
|
457
547
|
}
|
|
458
548
|
for (let i = 0; i < word.length; i += maxWidth) {
|
|
459
549
|
lines.push(word.substring(i, i + maxWidth));
|
|
@@ -481,27 +571,29 @@ export function displayAnnouncementDetail(announcement) {
|
|
|
481
571
|
const terminalWidth = process.stdout.columns || 80;
|
|
482
572
|
const boxWidth = Math.max(50, terminalWidth - 4);
|
|
483
573
|
const contentWidth = boxWidth - 4;
|
|
484
|
-
const title = announcement.title ||
|
|
485
|
-
const date = announcement.postedAt
|
|
486
|
-
|
|
487
|
-
|
|
574
|
+
const title = announcement.title || "Untitled";
|
|
575
|
+
const date = announcement.postedAt
|
|
576
|
+
? new Date(announcement.postedAt).toLocaleString()
|
|
577
|
+
: "N/A";
|
|
578
|
+
const author = announcement.author || "Unknown";
|
|
579
|
+
const cleanedMessage = cleanHtmlContent(announcement.message || "No content");
|
|
488
580
|
const titleLines = wordWrap(title, contentWidth);
|
|
489
581
|
const messageLines = wordWrap(cleanedMessage, contentWidth);
|
|
490
582
|
const printLine = (content, style = chalk.white) => {
|
|
491
583
|
const paddedContent = content.padEnd(contentWidth);
|
|
492
|
-
console.log(chalk.gray(
|
|
584
|
+
console.log(chalk.gray("│ ") + style(paddedContent) + chalk.gray(" │"));
|
|
493
585
|
};
|
|
494
|
-
console.log(
|
|
586
|
+
console.log("\n" + chalk.gray("╭" + "─".repeat(boxWidth - 2) + "╮"));
|
|
495
587
|
for (const line of titleLines) {
|
|
496
588
|
printLine(line, chalk.bold.white);
|
|
497
589
|
}
|
|
498
|
-
console.log(chalk.gray(
|
|
590
|
+
console.log(chalk.gray("├" + "─".repeat(boxWidth - 2) + "┤"));
|
|
499
591
|
printLine(`Posted: ${date}`, chalk.gray);
|
|
500
592
|
printLine(`Author: ${author}`, chalk.gray);
|
|
501
|
-
console.log(chalk.gray(
|
|
593
|
+
console.log(chalk.gray("├" + "─".repeat(boxWidth - 2) + "┤"));
|
|
502
594
|
for (const line of messageLines) {
|
|
503
595
|
printLine(line, chalk.white);
|
|
504
596
|
}
|
|
505
|
-
console.log(chalk.gray(
|
|
597
|
+
console.log(chalk.gray("╰" + "─".repeat(boxWidth - 2) + "╯") + "\n");
|
|
506
598
|
}
|
|
507
599
|
//# sourceMappingURL=display.js.map
|