diff --git a/src/main/webapp/app/exercises/quiz/manage/quiz-exercise.scss b/src/main/webapp/app/exercises/quiz/manage/quiz-exercise.scss
index 008f137b3cf9..4c084c80d989 100644
--- a/src/main/webapp/app/exercises/quiz/manage/quiz-exercise.scss
+++ b/src/main/webapp/app/exercises/quiz/manage/quiz-exercise.scss
@@ -88,6 +88,10 @@
.markupEditorArea {
margin-bottom: 14px;
+
+ .markdown-editor {
+ border: 1px solid var(--border-color);
+ }
}
.mapping-numbers-wrapper {
diff --git a/src/main/webapp/app/exercises/quiz/manage/quiz-question-list-edit.component.ts b/src/main/webapp/app/exercises/quiz/manage/quiz-question-list-edit.component.ts
index bf0754da46d0..3d3da811b347 100644
--- a/src/main/webapp/app/exercises/quiz/manage/quiz-question-list-edit.component.ts
+++ b/src/main/webapp/app/exercises/quiz/manage/quiz-question-list-edit.component.ts
@@ -169,7 +169,7 @@ export class QuizQuestionListEditComponent {
'Enter your long question if needed\n\n' +
'Select a part of the text and click on Add Spot to automatically create an input field and the corresponding mapping\n\n' +
'You can define a input field like this: This [-spot 1] an [-spot 2] field.\n\n' +
- 'To define the solution for the input fields you need to create a mapping (multiple mapping also possible):\n\n' +
+ 'To define the solution for the input fields you need to create a mapping (multiple mapping also possible):\n\n\n' +
'[-option 1] is\n' +
'[-option 2] input\n' +
'[-option 1,2] correctInBothFields';
diff --git a/src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.html b/src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.html
index 3fd0ce91a5b3..8372aea83c43 100644
--- a/src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.html
+++ b/src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.html
@@ -277,24 +277,18 @@
- @if (!reEvaluationInProgress) {
-
-
-
-
-
-
- }
@if (!reEvaluationInProgress) {
-
}
diff --git a/src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.ts b/src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.ts
index 6e99de7c0fb8..538966d0c15f 100644
--- a/src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.ts
+++ b/src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.ts
@@ -12,10 +12,6 @@ import {
ViewChild,
ViewEncapsulation,
} from '@angular/core';
-import { ArtemisMarkdownService } from 'app/shared/markdown.service';
-import { AceEditorComponent } from 'app/shared/markdown-editor/ace-editor/ace-editor.component';
-import 'brace/theme/chrome';
-import 'brace/mode/markdown';
import { ShortAnswerQuestionUtil } from 'app/exercises/quiz/shared/short-answer-question-util.service';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ShortAnswerQuestion } from 'app/entities/quiz/short-answer-question.model';
@@ -29,6 +25,19 @@ import { markdownForHtml } from 'app/shared/util/markdown.conversion.util';
import { generateExerciseHintExplanation, parseExerciseHintExplanation } from 'app/shared/util/markdown.util';
import { faAngleDown, faAngleRight, faBan, faBars, faChevronDown, faChevronUp, faTrash, faUndo, faUnlink } from '@fortawesome/free-solid-svg-icons';
import { MAX_QUIZ_QUESTION_POINTS, MAX_QUIZ_SHORT_ANSWER_TEXT_LENGTH } from 'app/shared/constants/input.constants';
+import { MarkdownEditorMonacoComponent } from 'app/shared/markdown-editor/monaco/markdown-editor-monaco.component';
+import { MarkdownEditorHeight } from 'app/shared/markdown-editor/markdown-editor.component';
+import { MonacoBoldAction } from 'app/shared/monaco-editor/model/actions/monaco-bold.action';
+import { MonacoItalicAction } from 'app/shared/monaco-editor/model/actions/monaco-italic.action';
+import { MonacoUnderlineAction } from 'app/shared/monaco-editor/model/actions/monaco-underline.action';
+import { MonacoCodeAction } from 'app/shared/monaco-editor/model/actions/monaco-code.action';
+import { MonacoUrlAction } from 'app/shared/monaco-editor/model/actions/monaco-url.action';
+import { MonacoUnorderedListAction } from 'app/shared/monaco-editor/model/actions/monaco-unordered-list.action';
+import { MonacoOrderedListAction } from 'app/shared/monaco-editor/model/actions/monaco-ordered-list.action';
+import { MonacoInsertShortAnswerSpotAction } from 'app/shared/monaco-editor/model/actions/quiz/monaco-insert-short-answer-spot.action';
+import { MonacoEditorAction } from 'app/shared/monaco-editor/model/actions/monaco-editor-action.model';
+import { MonacoInsertShortAnswerOptionAction } from 'app/shared/monaco-editor/model/actions/quiz/monaco-insert-short-answer-option.action';
+import { SHORT_ANSWER_QUIZ_QUESTION_EDITOR_OPTIONS } from 'app/shared/monaco-editor/monaco-editor-option.helper';
@Component({
selector: 'jhi-short-answer-question-edit',
@@ -38,12 +47,16 @@ import { MAX_QUIZ_QUESTION_POINTS, MAX_QUIZ_SHORT_ANSWER_TEXT_LENGTH } from 'app
})
export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, AfterViewInit, QuizQuestionEdit {
@ViewChild('questionEditor', { static: false })
- private questionEditor: AceEditorComponent;
+ private questionEditor: MarkdownEditorMonacoComponent;
@ViewChild('clickLayer', { static: false })
private clickLayer: ElementRef;
@ViewChild('question', { static: false })
questionElement: ElementRef;
+ markdownActions: MonacoEditorAction[];
+ insertShortAnswerOptionAction = new MonacoInsertShortAnswerOptionAction();
+ insertShortAnswerSpotAction = new MonacoInsertShortAnswerSpotAction(this.insertShortAnswerOptionAction);
+
shortAnswerQuestion: ShortAnswerQuestion;
@Input()
@@ -68,10 +81,7 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
readonly MAX_CHARACTER_COUNT = MAX_QUIZ_SHORT_ANSWER_TEXT_LENGTH;
- /** Ace Editor configuration constants **/
- questionEditorText: any = '';
- questionEditorMode = 'markdown';
- questionEditorAutoUpdate = true;
+ questionEditorText = '';
showVisualMode: boolean;
/** Status boolean for collapse status **/
@@ -80,8 +90,6 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
/** Variables needed for the setup of editorText **/
// equals the highest spotNr
numberOfSpot = 1;
- // defines the first gap between text and solutions when
- firstPressed = 1;
// has all solution options with their mapping (each spotNr)
optionsWithID: string[] = [];
@@ -101,16 +109,28 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
faAngleRight = faAngleRight;
faAngleDown = faAngleDown;
- readonly MAX_POINTS = MAX_QUIZ_QUESTION_POINTS;
+ protected readonly MAX_POINTS = MAX_QUIZ_QUESTION_POINTS;
+ protected readonly MarkdownEditorHeight = MarkdownEditorHeight;
constructor(
- private artemisMarkdown: ArtemisMarkdownService,
public shortAnswerQuestionUtil: ShortAnswerQuestionUtil,
private modalService: NgbModal,
private changeDetector: ChangeDetectorRef,
) {}
ngOnInit(): void {
+ this.markdownActions = [
+ new MonacoBoldAction(),
+ new MonacoItalicAction(),
+ new MonacoUnderlineAction(),
+ new MonacoCodeAction(),
+ new MonacoUrlAction(),
+ new MonacoUnorderedListAction(),
+ new MonacoOrderedListAction(),
+ this.insertShortAnswerSpotAction,
+ this.insertShortAnswerOptionAction,
+ ];
+
// create deepcopy
this.backupQuestion = cloneDeep(this.shortAnswerQuestion);
@@ -185,28 +205,9 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
setupQuestionEditor(): void {
// Sets the counter to the highest spotNr and generates solution options with their mapping (each spotNr)
this.numberOfSpot = this.shortAnswerQuestion.spots!.length + 1;
-
- // Default editor settings for inline markup editor
- this.questionEditor.getEditor().renderer.setShowGutter(false);
- this.questionEditor.getEditor().renderer.setPadding(10);
- this.questionEditor.getEditor().renderer.setScrollMargin(8, 8);
- this.questionEditor.getEditor().setHighlightActiveLine(false);
- this.questionEditor.getEditor().setShowPrintMargin(false);
-
+ this.questionEditor.applyOptionPreset(SHORT_ANSWER_QUIZ_QUESTION_EDITOR_OPTIONS);
// Generate markdown from question and show result in editor
this.questionEditorText = this.generateMarkdown();
- this.questionEditor.getEditor().clearSelection();
-
- // Register the onBlur listener
- this.questionEditor.getEditor().on(
- 'blur',
- () => {
- // Parse the markdown in the editor and update question accordingly
- this.parseMarkdown(this.questionEditorText);
- this.questionUpdated.emit();
- },
- this,
- );
this.changeDetector.detectChanges();
this.parseMarkdown(this.questionEditorText);
this.questionUpdated.emit();
@@ -233,7 +234,7 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
option += ',' + this.shortAnswerQuestion.spots?.filter((spot) => this.shortAnswerQuestionUtil.isSameSpot(spot, spotForSolution))[0].spotNr;
}
});
- option += ']';
+ option += option === '[-option ' ? '#]' : ']';
this.optionsWithID.push(option);
});
}
@@ -247,10 +248,11 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
*/
generateMarkdown(): string {
this.setOptionsWithID();
- const markdownText =
- generateExerciseHintExplanation(this.shortAnswerQuestion) +
- '\n\n\n' +
- this.shortAnswerQuestion.solutions?.map((solution, index) => this.optionsWithID[index] + ' ' + solution.text!.trim()).join('\n');
+ let markdownText = generateExerciseHintExplanation(this.shortAnswerQuestion);
+
+ if (this.shortAnswerQuestion.solutions?.length) {
+ markdownText += '\n\n\n' + this.shortAnswerQuestion.solutions.map((solution, index) => this.optionsWithID[index] + ' ' + solution.text!.trim()).join('\n');
+ }
return markdownText;
}
@@ -354,36 +356,17 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
* an option connected to the spot below the last visible row
*/
addSpotAtCursor(): void {
- const editor = this.questionEditor.getEditor();
- const optionText = editor.getCopyText();
- const currentSpotNumber = this.numberOfSpot;
- const addedText = '[-spot ' + currentSpotNumber + ']';
- editor.focus();
- editor.insert(addedText);
- editor.moveCursorTo(editor.getLastVisibleRow() + this.numberOfSpot, Number.POSITIVE_INFINITY);
- this.addOptionToSpot(editor, currentSpotNumber, optionText, this.firstPressed);
-
- this.firstPressed++;
+ this.insertShortAnswerSpotAction.executeInCurrentEditor({ spotNumber: this.numberOfSpot });
}
/**
* add the markdown for a solution option below the last visible row, which is connected to a spot in the given editor
*
- * @param editor {object} the editor into which the solution option markdown will be inserted
- * @param numberOfSpot
- * @param optionText
- * @param firstPressed
- */
- addOptionToSpot(editor: any, numberOfSpot: number, optionText: string, firstPressed: number) {
- let addedText: string;
- if (numberOfSpot === 1 && firstPressed === 1) {
- addedText = '\n\n\n[-option ' + numberOfSpot + '] ' + optionText;
- } else {
- addedText = '\n[-option ' + numberOfSpot + '] ' + optionText;
- }
- editor.focus();
- editor.clearSelection();
- editor.insert(addedText);
+ * @param numberOfSpot the number of the spot to which the option should be connected
+ * @param optionText the text of the option
+ */
+ addOptionToSpot(numberOfSpot: number, optionText: string) {
+ this.insertShortAnswerOptionAction.executeInCurrentEditor({ spotNumber: numberOfSpot, optionText });
}
/**
@@ -391,21 +374,7 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
* @desc Add the markdown for a solution option below the last visible row
*/
addOption(): void {
- const editor = this.questionEditor.getEditor();
- let addedText: string;
- if (this.firstPressed === 1) {
- addedText = '\n\n\n[-option #] Please enter here one answer option and do not forget to replace # with a number';
- } else {
- addedText = '\n[-option #] Please enter here one answer option and do not forget to replace # with a number';
- }
- editor.clearSelection();
- editor.moveCursorTo(editor.getLastVisibleRow(), Number.POSITIVE_INFINITY);
- editor.insert(addedText);
- const range = editor.selection.getRange();
- range.setStart(range.start.row, 12);
- editor.selection.setRange(range);
-
- this.firstPressed++;
+ this.insertShortAnswerOptionAction.executeInCurrentEditor();
}
/**
@@ -426,7 +395,6 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
return;
}
- const editor = this.questionEditor.getEditor();
// ID 'element-row-column' is divided into array of [row, column]
const selectedTextRowColumn = selection.focusNode!.parentNode!.parentElement!.id.split('-').slice(1);
@@ -458,8 +426,8 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
const currentSpotNumber = this.numberOfSpot;
// split text before first option tag
- const questionText = editor
- .getValue()
+ const questionText = this.questionEditor.monacoEditor
+ .getText()
.split(/\[-option /g)[0]
.trim();
this.textParts = this.shortAnswerQuestionUtil.divideQuestionTextIntoTextParts(questionText);
@@ -470,12 +438,9 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
this.shortAnswerQuestion.text = this.textParts.map((textPart) => textPart.join(' ')).join('\n');
const textParts = this.shortAnswerQuestionUtil.divideQuestionTextIntoTextParts(this.shortAnswerQuestion.text);
this.textParts = this.shortAnswerQuestionUtil.transformTextPartsIntoHTML(textParts);
- editor.setValue(this.generateMarkdown());
- editor.moveCursorTo(editor.getLastVisibleRow() + currentSpotNumber, Number.POSITIVE_INFINITY);
- this.addOptionToSpot(editor, currentSpotNumber, markedText, this.firstPressed);
- this.parseMarkdown(editor.getValue());
-
- this.firstPressed++;
+ this.setQuestionEditorValue(this.generateMarkdown());
+ this.addOptionToSpot(currentSpotNumber, markedText);
+ this.parseMarkdown(this.questionEditor.monacoEditor.getText());
this.questionUpdated.emit();
}
@@ -490,9 +455,8 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
this.shortAnswerQuestion.solutions = [];
}
const solution = new ShortAnswerSolution();
- solution.text = 'Please enter here your text';
- this.shortAnswerQuestion.solutions.push(solution);
- this.questionEditorText = this.generateMarkdown();
+ solution.text = MonacoInsertShortAnswerOptionAction.DEFAULT_TEXT_SHORT;
+ this.insertShortAnswerOptionAction.executeInCurrentEditor({ optionText: solution.text });
this.questionUpdated.emit();
}
@@ -636,8 +600,7 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
const textParts = this.shortAnswerQuestionUtil.divideQuestionTextIntoTextParts(this.shortAnswerQuestion.text!);
this.textParts = this.shortAnswerQuestionUtil.transformTextPartsIntoHTML(textParts);
- this.questionEditor.getEditor().setValue(this.generateMarkdown());
- this.questionEditor.getEditor().clearSelection();
+ this.setQuestionEditorValue(this.generateMarkdown());
}
/**
@@ -767,7 +730,9 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
}
onTextChange(newText: string) {
+ this.parseMarkdown(this.questionEditorText);
this.numberOfSpot = this.getHighestSpotNumbers(newText) + 1;
+ this.insertShortAnswerSpotAction.spotNumber = this.numberOfSpot;
this.questionUpdated.emit();
}
@@ -786,6 +751,6 @@ export class ShortAnswerQuestionEditComponent implements OnInit, OnChanges, Afte
}
setQuestionEditorValue(text: string): void {
- this.questionEditor.getEditor().setValue(text);
+ this.questionEditor.markdown = text;
}
}
diff --git a/src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.html b/src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.html
index 27cd1906c609..075014915cf8 100644
--- a/src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.html
+++ b/src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.html
@@ -2,14 +2,15 @@