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"
3440import { useDialogPluginComponent } from ' quasar' ;
3541import { Ctf , Task } from ' src/ctfnote/models' ;
3642import { defineComponent , ref } from ' vue' ;
43+ import * as JSZip from ' jszip' ;
3744
3845export 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 },
0 commit comments