Skip to content

Commit d845b3e

Browse files
JJ-8lkempf
authored andcommitted
Add zip export
This adds the possibility to use a zip archive as the export format. It will add every task as a Markdown file to the zip archive and create an overview file which contains the same introduction as the Markdown export. The original single-markdown export is still possible and the default. To create the zip archive, the dependency [JSZip](https://www.npmjs.com/package/jszip) is used. This feature was also already suggested in TFNS#185 (review).
1 parent ecde846 commit d845b3e

File tree

3 files changed

+109
-20
lines changed

3 files changed

+109
-20
lines changed

front/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"color-hash": "^2.0.1",
2525
"core-js": "^3.6.5",
2626
"hotkeys-js": "^3.8.7",
27+
"jszip": "^3.10.1",
2728
"quasar": "^2.0.0",
2829
"slugify": "^1.6.1",
2930
"ts-essentials": "^9.0.0",

front/src/components/Dialogs/TaskExportDialog.vue

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@
99
<q-card-section>
1010
<div>
1111
<q-select
12-
v-model="currentOption"
12+
v-model="currentFormatOption"
13+
class="full-width"
14+
label="Export Format"
15+
:options="exportFormats"
16+
/>
17+
<q-select
18+
v-model="currentTypeOption"
1319
class="full-width"
1420
label="Export Type"
1521
:options="exportOptions"
@@ -34,6 +40,7 @@
3440
import { useDialogPluginComponent } from 'quasar';
3541
import { Ctf, Task } from 'src/ctfnote/models';
3642
import { defineComponent, ref } from 'vue';
43+
import * as JSZip from 'jszip';
3744
3845
export default defineComponent({
3946
props: {
@@ -46,11 +53,15 @@ export default defineComponent({
4653
4754
const exportOptions = ['Solved tasks only', 'All tasks'];
4855
56+
const exportFormats = ['Markdown', 'Zip'];
57+
4958
return {
5059
model: ref(''),
51-
currentOption: ref(exportOptions[0]),
60+
currentTypeOption: ref(exportOptions[0]),
61+
currentFormatOption: ref(exportFormats[0]),
5262
teamName: ref(''),
5363
exportOptions,
64+
exportFormats,
5465
dialogRef,
5566
onDialogHide,
5667
onDialogOK,
@@ -74,7 +85,23 @@ export default defineComponent({
7485
return markdown;
7586
},
7687
77-
async exportTasks(tasks: Task[]) {
88+
downloadBlob(blob: Blob) {
89+
const link = document.createElement('a');
90+
link.href = URL.createObjectURL(blob);
91+
92+
let extension = '';
93+
if (this.currentFormatOption == 'Markdown') {
94+
extension = 'md';
95+
} else if (this.currentFormatOption == 'Zip') {
96+
extension = 'zip';
97+
}
98+
99+
link.download = `${this.ctf.title}.${extension}`;
100+
link.click();
101+
URL.revokeObjectURL(link.href);
102+
},
103+
104+
createIntroduction(tasks: Task[]): string {
78105
let template = '';
79106
80107
// Add CTF title
@@ -95,6 +122,12 @@ export default defineComponent({
95122
.join('\n');
96123
}
97124
125+
return template;
126+
},
127+
128+
async exportTasksAsMarkdown(tasks: Task[]) {
129+
let template = this.createIntroduction(tasks);
130+
98131
template += '\n\n\n';
99132
100133
// Add tasks
@@ -104,32 +137,60 @@ export default defineComponent({
104137
)
105138
).join('\n\n');
106139
107-
// download
108140
const blob = new Blob([template], {
109141
type: 'text/markdown',
110142
});
111-
const link = document.createElement('a');
112-
link.href = URL.createObjectURL(blob);
113-
link.download = this.ctf.title + '.md';
114-
link.click();
115-
URL.revokeObjectURL(link.href);
143+
144+
this.downloadBlob(blob);
145+
},
146+
147+
async exportTasksAsZip(tasks: Task[]) {
148+
const zip = new JSZip.default();
149+
150+
const files = await Promise.all(
151+
tasks.flatMap(async (task) => {
152+
return {
153+
title: task.title,
154+
content: await this.downloadTaskMarkdown(task),
155+
};
156+
}, this)
157+
);
158+
159+
zip.file(
160+
`${this.ctf.title} - Overview.md`,
161+
this.createIntroduction(tasks)
162+
);
163+
164+
for (const f of files) {
165+
zip.file(f.title + '.md', f.content);
166+
}
167+
168+
const zipBlob = await zip.generateAsync({ type: 'blob' });
169+
170+
this.downloadBlob(zipBlob);
116171
},
117172
118173
async btnClick() {
119174
let tasks = this.ctf.tasks;
120-
if (this.currentOption == 'Solved tasks only') {
175+
if (this.currentTypeOption == 'Solved tasks only') {
121176
tasks = tasks.filter((t) => t.solved);
122177
}
123178
this.loading = true;
124-
await this.exportTasks(
125-
tasks
126-
.sort((a, b) =>
127-
a.title.toLowerCase().localeCompare(b.title.toLowerCase())
128-
)
129-
.sort((a, b) =>
130-
a.category.toLowerCase().localeCompare(b.category.toLowerCase())
131-
)
132-
);
179+
180+
const sortedTasks = tasks
181+
.sort((a, b) =>
182+
a.title.toLowerCase().localeCompare(b.title.toLowerCase())
183+
)
184+
.sort((a, b) =>
185+
a.category.toLowerCase().localeCompare(b.category.toLowerCase())
186+
);
187+
188+
if (this.currentFormatOption == 'Markdown') {
189+
await this.exportTasksAsMarkdown(sortedTasks);
190+
} else if (this.currentFormatOption == 'Zip') {
191+
await this.exportTasksAsZip(sortedTasks);
192+
}
193+
133194
this.loading = false;
134195
this.onDialogOK();
135196
},

front/yarn.lock

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5570,6 +5570,11 @@ ignore@^5.1.4, ignore@^5.1.9, ignore@^5.2.0:
55705570
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a"
55715571
integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==
55725572

5573+
immediate@~3.0.5:
5574+
version "3.0.6"
5575+
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
5576+
integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==
5577+
55735578
immutable@~3.7.6:
55745579
version "3.7.6"
55755580
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
@@ -6101,6 +6106,16 @@ jsonwebtoken@^8.5.1:
61016106
ms "^2.1.1"
61026107
semver "^5.6.0"
61036108

6109+
jszip@^3.10.1:
6110+
version "3.10.1"
6111+
resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2"
6112+
integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==
6113+
dependencies:
6114+
lie "~3.3.0"
6115+
pako "~1.0.2"
6116+
readable-stream "~2.3.6"
6117+
setimmediate "^1.0.5"
6118+
61046119
jwa@^1.4.1:
61056120
version "1.4.1"
61066121
resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a"
@@ -6177,6 +6192,13 @@ levn@^0.4.1:
61776192
prelude-ls "^1.2.1"
61786193
type-check "~0.4.0"
61796194

6195+
lie@~3.3.0:
6196+
version "3.3.0"
6197+
resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a"
6198+
integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==
6199+
dependencies:
6200+
immediate "~3.0.5"
6201+
61806202
lilconfig@^2.0.3:
61816203
version "2.0.6"
61826204
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4"
@@ -7112,6 +7134,11 @@ package-json@^6.3.0:
71127134
registry-url "^5.0.0"
71137135
semver "^6.2.0"
71147136

7137+
pako@~1.0.2:
7138+
version "1.0.11"
7139+
resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf"
7140+
integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==
7141+
71157142
param-case@^2.1.1:
71167143
version "2.1.1"
71177144
resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247"
@@ -7710,7 +7737,7 @@ read-metadata@^1.0.0:
77107737
dependencies:
77117738
yaml-js "0.0.8"
77127739

7713-
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.3.0, readable-stream@^2.3.5:
7740+
readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.5, readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6:
77147741
version "2.3.7"
77157742
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
77167743
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==

0 commit comments

Comments
 (0)