Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Communication: Add additional input formatting options #9657

Merged

Conversation

asliayk
Copy link
Contributor

@asliayk asliayk commented Nov 2, 2024

Checklist

General

Client

  • I strictly followed the client coding and design guidelines.
  • I added multiple integration tests (Jest) related to the features (with a high test coverage), while following the test guidelines.
  • I documented the TypeScript code using JSDoc style.
  • I added multiple screenshots/screencasts of my UI changes.
  • I translated all newly inserted strings into English and German.

Motivation and Context

  • In communication module, the input formatting options, such as strike-through, unordered lists, and bullet lists that are available in Slack, are missing.
  • Even though the user can add new lines in the editor, when s/he sends the message, the line breaks don’t show up.
  • When converting text to emojis automatically, unintended strings like "c:" are also being converted, leading to undesired behavior.

Description

  • Added support for strikethrough, bulleted list, and ordered list options in the communication module.
  • New lines can now be used in messages, allowing for line breaks.
  • Bulleted and ordered lists were previously available on some other pages containing the editor but did not function correctly. Issues such as Shift+Enter not adding a prefix automatically, Backspace not working properly, and prefix nesting problems when working with nested lists or switching list formats have been resolved in the new action classes.
  • Now, only strings that begin with a colon (:) are converted to emojis.

Steps for Testing

Prerequisites:

  • 1 Instructor/Student
  • 1 Course with Communication enabled
  1. Log in to Artemis
  2. Navigate to Communication section of a course
  3. Open a conversation and try new options on input area: strikethrough, bulleted list and ordered list
  4. Send a message that contains a line break
  5. Send a message that contains "c:" and see that it does not converted to an emoji anymore

Testserver States

Note

These badges show the state of the test servers.
Green = Currently available, Red = Currently locked
Click on the badges to get to the test servers.







Review Progress

Code Review

  • Code Review 1
  • Code Review 2

Manual Tests

  • Test 1
  • Test 2

Test Coverage

Class/File Line Coverage Confirmation (assert/expect)
short-answer-question-edit.component.ts 95.48% ✅ ❌
markdown-editor-monaco.component.ts 95.38% ✅ ❌
posting-content-part.components.ts 95.65% ✅ ❌
posting-markdown-editor.component.ts 100%
bulleted-list.action.ts 100%
list.action.ts 55.55% ✅ ❌
ordered-list.action.ts 100%
strikethrough.action.ts 75% ✅ ❌
monaco-editor.component.ts 94.66% ✅ ❌

Screenshots

new formatting options on editor
image
strikethrough example
image

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced markdown editor with new formatting options: Bulleted lists and strikethrough text.
    • Improved handling of content rendering with whitespace management in posting content.
    • New processing logic for handling numbered lists in posting content.
  • Bug Fixes

    • Refined emoji conversion logic to target specific emoticons in text.
  • Documentation

    • Updated localization files to include new strikethrough formatting options in both English and German.
  • Tests

    • Added new test cases for validating bulleted and ordered list actions in the markdown editor.

@asliayk asliayk requested a review from a team as a code owner November 2, 2024 22:42
@asliayk asliayk marked this pull request as draft November 2, 2024 22:42
@github-actions github-actions bot added tests client Pull requests that update TypeScript code. (Added Automatically!) labels Nov 2, 2024
@asliayk asliayk marked this pull request as ready for review November 2, 2024 23:48
Copy link

coderabbitai bot commented Nov 2, 2024

Walkthrough

This pull request introduces several modifications to the text editing capabilities within the Angular application, particularly focusing on the ShortAnswerQuestionEditComponent and related markdown editor components. Key changes include the addition of BulletedListAction and StrikethroughAction, while UnorderedListAction has been removed from the available actions. Additionally, the HTML rendering of content in posting-content-part.component.html has been updated to process whitespace differently. Several new methods and properties have been added to enhance functionality, and localization files have been updated to include new entries for strikethrough formatting.

Changes

File Change Summary
src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.ts Added BulletedListAction and StrikethroughAction to markdownActions, removed UnorderedListAction.
src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.ts Replaced UnorderedListAction with BulletedListAction, added StrikethroughAction to defaultActions.
src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.component.html Updated HTML to use processedContentBeforeReference and processedContentAfterReference with white-space: pre-wrap.
src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.components.ts Implemented OnInit, added ngOnInit, processContent, and escapeNumberedList methods, modified allowedHtmlTags.
src/main/webapp/app/shared/metis/posting-markdown-editor/posting-markdown-editor.component.ts Added BulletedListAction, OrderedListAction, and StrikethroughAction to defaultActions, defined activeConversation.
src/main/webapp/app/shared/monaco-editor/model/actions/bulleted-list.action.ts Introduced BulletedListAction class for managing bullet lists.
src/main/webapp/app/shared/monaco-editor/model/actions/list.action.ts Added abstract ListAction class for list management in the text editor.
src/main/webapp/app/shared/monaco-editor/model/actions/ordered-list.action.ts Updated OrderedListAction to extend ListAction, modified ID and added getPrefix method.
src/main/webapp/app/shared/monaco-editor/model/actions/strikethrough.action.ts Introduced StrikethroughAction class for toggling strikethrough formatting.
src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts Modified convertTextToEmoji method to refine emoji conversion logic.
src/main/webapp/i18n/de/multipleChoiceQuestion.json Added "strikethrough": "Durchgestrichen" to localization strings.
src/main/webapp/i18n/en/multipleChoiceQuestion.json Added "strikethrough": "Strikethrough" to localization strings.
src/test/javascript/spec/component/shared/metis/postings-markdown-editor/postings-markdown-editor.component.spec.ts Updated tests to include new actions, added tests for list actions.
src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action.integration.spec.ts Modified tests for ordered lists, changed expected outputs.

Possibly related PRs

Suggested labels

small, bugfix

Suggested reviewers

  • florian-glombik
  • JohannesStoehr
  • sachmii
  • Malekos74
  • HawKhiem
  • krusche

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai or @coderabbitai title anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Outside diff range and nitpick comments (16)
src/main/webapp/app/shared/monaco-editor/model/actions/bulleted-list.action.ts (2)

4-4: Consider improving constant naming and accessibility

The bullet character (•) might cause accessibility issues with screen readers. Consider using a more accessible ASCII character like '*' or '-'.

-const BULLET_PREFIX = '•  ';
+const BULLET_LIST_PREFIX = '*  ';

6-9: Enhance JSDoc documentation

While the documentation is clear, it would be helpful to include:

  • @example showing usage
  • @see reference to ListAction base class
  • Description of how bullet points are handled
 /**
  * Action used to add or modify a bullet-point list in the text editor.
+ * 
+ * @example
+ * ```typescript
+ * const action = new BulletedListAction();
+ * action.run(editor); // Adds/toggles bullet points for selected lines
+ * ```
+ * 
+ * @see ListAction The base class providing common list functionality
  */
src/main/webapp/app/shared/monaco-editor/model/actions/strikethrough.action.ts (2)

7-11: Enhance class documentation with more details.

While the basic documentation is present, it would be beneficial to add:

  • @example showing usage
  • @see reference to related markdown documentation
  • Description of when this action is available/enabled

Here's a suggested enhancement:

 /**
- * Action to toggle strikethrough text in the editor.
+ * Action to toggle strikethrough text formatting in the editor using markdown syntax.
+ * 
+ * @example
+ * // Selected text: "example"
+ * // After action: "~~example~~"
+ * 
+ * @see https://www.markdownguide.org/extended-syntax/#strikethrough
  */

1-26: Well-designed action class following the Command pattern.

The implementation follows good architectural principles:

  • Encapsulation of formatting logic in a dedicated action class
  • Clean integration with Monaco editor through the TextEditor interface
  • Consistent with other formatting actions in the codebase

Consider adding an interface for all formatting actions to enforce consistent implementation across similar features (strikethrough, bold, italic, etc.).

src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.component.html (1)

Line range hint 1-79: Consider splitting component responsibilities

The component currently handles multiple concerns including content processing, reference handling, image management, and event handling. Consider splitting these responsibilities into separate components or services for better maintainability and testing:

  1. Create a dedicated content processor service
  2. Extract image handling into a separate component
  3. Move reference handling logic to a dedicated service

This would align with the Single Responsibility Principle and make the codebase more maintainable as new formatting options are added.

src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.components.ts (3)

48-49: Add type annotations for processed content properties.

For better type safety and code maintainability, explicitly declare the types of the processed content properties.

-    processedContentBeforeReference: string;
-    processedContentAfterReference: string;
+    processedContentBeforeReference: string | undefined;
+    processedContentAfterReference: string | undefined;

57-59: Consider implementing OnDestroy for cleanup.

To prevent memory leaks, implement OnDestroy to clean up any subscriptions and event listeners, particularly for userReferenceClicked and channelReferenceClicked EventEmitters.

-export class PostingContentPartComponent implements OnInit {
+export class PostingContentPartComponent implements OnInit, OnDestroy {
+    ngOnDestroy() {
+        // Clean up subscriptions and event listeners
+    }

84-86: Enhance numbered list processing for edge cases.

The current regex pattern might miss some edge cases. Consider:

  1. Multiple digits at the start
  2. Different types of whitespace
  3. Empty lines between list items
     escapeNumberedList(content: string): string {
-        return content.replace(/^(\s*\d+)\. /gm, '$1\\. ');
+        if (!content) return '';
+        return content.replace(/^([ \t]*\d+)\. (?=\S)/gm, '$1\\. ');
     }
src/main/webapp/app/shared/metis/posting-markdown-editor/posting-markdown-editor.component.ts (1)

Line range hint 91-115: Consider refactoring action initialization for better maintainability.

As the number of available actions grows, consider implementing a factory pattern or using dependency injection for action initialization. This would improve maintainability and make it easier to add/remove actions in the future.

Example approach:

// Create an action factory service
@Injectable()
class TextEditorActionFactory {
  createActions(options: EditorOptions): TextEditorAction[] {
    return [
      // Basic formatting
      this.createBasicFormattingActions(),
      // List formatting
      this.createListFormattingActions(),
      // Advanced formatting
      this.createAdvancedFormattingActions(),
      // Special actions
      this.createSpecialActions(options),
    ].flat();
  }
}
src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts (1)

113-118: Consider optimizing for performance.

For better performance in real-time editing scenarios, consider:

  1. Using regex replacement instead of split/join operations
  2. Implementing lazy evaluation for large texts
  3. Adding input validation

Here's a more efficient implementation:

 convertTextToEmoji(text: string): string {
-    const words = text.split(' ');
-    const convertedWords = words.map((word) => {
-        return word.startsWith(':') ? this.emojiConvertor.replace_emoticons(word) : word;
-    });
-
-    return convertedWords.join(' ');
+    if (!text?.trim()) {
+        return text;
+    }
+    // Use regex to find and replace only words starting with ':'
+    return text.replace(/(\s|^):[^\s]+/g, (match) => {
+        const converted = this.emojiConvertor.replace_emoticons(match);
+        // If no conversion occurred, return original match
+        return converted === match ? match : converted;
+    });
 }
src/test/javascript/spec/component/shared/metis/postings-markdown-editor/postings-markdown-editor.component.spec.ts (1)

335-376: Consider enhancing the helper function with types and documentation.

The simulateListAction helper is well-implemented but could be more maintainable with:

  1. TypeScript type annotations
  2. JSDoc documentation
  3. Separated verification logic
+/**
+ * Simulates a list action on the mock editor and verifies the result
+ * @param action - The text editor action to simulate
+ * @param selectedText - The text currently selected in the editor
+ * @param expectedText - The expected text after the action is applied
+ * @param startLineNumber - The starting line number (default: 1)
+ */
-const simulateListAction = (action: TextEditorAction, selectedText: string, expectedText: string, startLineNumber: number = 1) => {
+const simulateListAction = (
+    action: TextEditorAction,
+    selectedText: string,
+    expectedText: string,
+    startLineNumber: number = 1
+): void => {
     // ... setup code ...

-    action.run(mockEditor);
+    action.run(mockEditor);
+    verifyActionResult(mockEditor, expectedText);
+};
+
+/**
+ * Verifies the result of a list action
+ * @param editor - The mock editor instance
+ * @param expectedText - The expected text after the action
+ */
+const verifyActionResult = (editor: jest.Mocked<TextEditor>, expectedText: string): void => {
     const replaceCalls = mockEditor.replaceTextAtRange.mock.calls;
     expect(replaceCalls).toHaveLength(1);

     const [range, text] = replaceCalls[0];
     // ... verification code ...
 };
src/main/webapp/app/shared/monaco-editor/model/actions/ordered-list.action.ts (3)

5-5: Ensure accuracy in the class documentation

The class documentation states "Action used to add or modify a numbered list in the text editor." Ensure that this description accurately reflects the functionality of the OrderedListAction class.


8-8: Ensure consistent naming between ID and class name

The ID is set to 'numberedList.action', but the class is named OrderedListAction. For consistency and clarity, consider aligning the ID with the class name.

Apply this diff to synchronize the ID with the class name:

-static readonly ID = 'numberedList.action';
+static readonly ID = 'orderedList.action';

19-19: Remove unused PREFIX property

The PREFIX property is declared but not assigned or utilized within the class. If it's unnecessary, consider removing it to keep the code clean.

Apply this diff to remove the unused property:

-protected readonly PREFIX: string;
src/main/webapp/app/shared/monaco-editor/model/actions/list.action.ts (2)

102-107: [Refactor Suggestion] Avoid hardcoding prefix values to enhance maintainability

The code is using a hardcoded value '• ' to determine whether the list is numbered or bulleted, which can lead to maintenance issues and makes the code less flexible. Instead, consider introducing an isNumberedList method or property to encapsulate this logic. This change will make the code more readable and easier to extend in the future.


39-46: [Refactor Suggestion] Extract common regular expressions into constants

The regular expressions used to detect list prefixes are repeated in multiple methods (stripAnyListPrefix, hasPrefix, and handleBackspace). To adhere to the DRY (Don't Repeat Yourself) principle and improve maintainability, consider extracting these regular expressions into constants or utility functions. This will make any future changes easier and reduce the risk of inconsistencies.

Also applies to: 132-135, 179-190

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4954074 and c702918.

📒 Files selected for processing (14)
  • src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.ts (2 hunks)
  • src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.ts (2 hunks)
  • src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.component.html (2 hunks)
  • src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.components.ts (5 hunks)
  • src/main/webapp/app/shared/metis/posting-markdown-editor/posting-markdown-editor.component.ts (2 hunks)
  • src/main/webapp/app/shared/monaco-editor/model/actions/bulleted-list.action.ts (1 hunks)
  • src/main/webapp/app/shared/monaco-editor/model/actions/list.action.ts (1 hunks)
  • src/main/webapp/app/shared/monaco-editor/model/actions/ordered-list.action.ts (1 hunks)
  • src/main/webapp/app/shared/monaco-editor/model/actions/strikethrough.action.ts (1 hunks)
  • src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts (1 hunks)
  • src/main/webapp/i18n/de/multipleChoiceQuestion.json (1 hunks)
  • src/main/webapp/i18n/en/multipleChoiceQuestion.json (1 hunks)
  • src/test/javascript/spec/component/shared/metis/postings-markdown-editor/postings-markdown-editor.component.spec.ts (5 hunks)
  • src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action.integration.spec.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (13)
src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.component.html (1)

Pattern src/main/webapp/**/*.html: @if and @for are new and valid Angular syntax replacing *ngIf and *ngFor. They should always be used over the old style.

src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.components.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/metis/posting-markdown-editor/posting-markdown-editor.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/monaco-editor/model/actions/bulleted-list.action.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/monaco-editor/model/actions/list.action.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/monaco-editor/model/actions/ordered-list.action.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/monaco-editor/model/actions/strikethrough.action.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts (1)

Pattern src/main/webapp/**/*.ts: angular_style:https://angular.io/guide/styleguide;methods_in_html:false;lazy_loading:true;code_reuse:true;tests:meaningful;types:PascalCase;enums:PascalCase;funcs:camelCase;props:camelCase;no_priv_prefix:true;strings:single_quotes;localize:true;btns:functionality;links:navigation;icons_text:newline;labels:associate;code_style:arrow_funcs,curly_braces,open_braces_same_line,indent_4;memory_leak_prevention:true;routes:naming_schema;chart_framework:ngx-charts;responsive_layout:true

src/main/webapp/i18n/de/multipleChoiceQuestion.json (1)

Pattern src/main/webapp/i18n/de/**/*.json: German language translations should be informal (dutzen) and should never be formal (sietzen). So the user should always be addressed with "du/dein" and never with "sie/ihr".

src/test/javascript/spec/component/shared/metis/postings-markdown-editor/postings-markdown-editor.component.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action.integration.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

🔇 Additional comments (25)
src/main/webapp/app/shared/monaco-editor/model/actions/bulleted-list.action.ts (1)

14-16: Clarify the purpose of undefined parameter in super call

The constructor passes undefined as the last parameter to the base class. Please document why this parameter is undefined and what it represents.

src/main/webapp/app/shared/monaco-editor/model/actions/strikethrough.action.ts (3)

1-5: LGTM! Well-organized imports and properly defined constant.

The imports are specific and follow Angular guidelines. The strikethrough delimiter constant is appropriately defined and follows naming conventions.


13-15: Verify localization key and clarify undefined parameter.

The constructor follows the parent class structure, but there are two points to verify:

  1. Ensure the localization key exists in the translation files
  2. The purpose of the undefined parameter should be documented

17-25: LGTM! Clean implementation with good documentation.

The run method is well-implemented with proper focus management. Consider adding @returns void to complete the JSDoc.

✅ Verification successful

Based on the search results, I can now generate a final response. We found the base TextEditorAction class and its implementation of toggleDelimiterAroundSelection which is used by all text formatting actions including StrikethroughAction. The implementation shows proper handling of text selection, delimiter toggling, and cursor positioning.

Implementation verified and follows established patterns

The StrikethroughAction correctly inherits from TextEditorAction and uses the shared toggleDelimiterAroundSelection method which:

  • Handles text selection state
  • Toggles delimiters (adds/removes them)
  • Maintains proper cursor position
  • Focuses the editor after the operation

The suggestion to add @returns void to JSDoc is valid but optional since TypeScript already shows the return type.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the behavior of toggleDelimiterAroundSelection in parent class

# Test: Look for the implementation of toggleDelimiterAroundSelection
ast-grep --pattern 'toggleDelimiterAroundSelection($$$) {
  $$$
}'

Length of output: 68


Script:

#!/bin/bash
# Let's try to find the parent class and the method implementation using ripgrep
rg "toggleDelimiterAroundSelection" -A 10

Length of output: 6123


Script:

#!/bin/bash
# Let's examine the parent class TextEditorAction to understand the inheritance
rg "class.*TextEditorAction" -A 5

# And also check if there are any tests for the strikethrough action
rg "describe.*StrikethroughAction" -A 10

Length of output: 17174

src/main/webapp/i18n/en/multipleChoiceQuestion.json (1)

49-50: LGTM! The localization entries are well-structured.

The new strikethrough entry follows consistent formatting and naming conventions, properly integrating with existing editor options.

src/main/webapp/i18n/de/multipleChoiceQuestion.json (1)

49-50: LGTM! Translation and syntax are correct.

The German translation "Durchgestrichen" for strikethrough is accurate and consistent with the style of other formatting options. The JSON syntax is valid with the proper comma placement.

✅ Verification successful

Translation and JSON syntax are verified and correct

The verification confirms:

  • JSON syntax is valid in the German translation file
  • "Durchgestrichen" correctly translates the English "Strikethrough"
  • The translation maintains consistency with other formatting options in the editor section
  • The informal style matches the rest of the file (e.g., "Du" is used elsewhere)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify JSON syntax
jq '.' src/main/webapp/i18n/de/multipleChoiceQuestion.json

# Check if the same key exists in English file for consistency
jq -r '.artemisApp.multipleChoiceQuestion.editor.strikethrough' src/main/webapp/i18n/en/multipleChoiceQuestion.json

Length of output: 2715

src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.component.html (3)

1-1: Excellent use of new Angular control flow syntax!

The template consistently uses the new @if syntax instead of the older *ngIf, which aligns perfectly with the coding guidelines.

Also applies to: 8-8, 14-14, 18-18, 22-22, 26-26, 29-29, 39-39, 47-47, 74-74


75-79: LGTM: Consistent implementation for after-reference content

The implementation mirrors the before-reference section, maintaining consistency in how content is processed and displayed.


2-6: LGTM: Content processing enhancement for line breaks

The addition of white-space: pre-wrap and use of processedContentBeforeReference aligns with the PR objective of preserving line breaks in messages. The htmlForPostingMarkdown pipe ensures proper rendering of the new formatting options.

Let's verify the content processing implementation:

✅ Verification successful

Based on the gathered information, I can now provide a final response:

Content processing implementation verified successfully

The implementation correctly handles line breaks and content processing through:

  • processedContentBeforeReference is properly processed using escapeNumberedList method
  • The white-space: pre-wrap style works in conjunction with the markdown processing
  • The htmlForPostingMarkdown pipe correctly transforms the content with proper sanitization and markdown conversion
  • Allowed HTML tags include 'br' and other formatting elements needed for proper rendering
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of content processing in the component
# Expected: Find the content processing logic that handles the new formatting

# Look for the component class implementation
ast-grep --pattern 'class PostingContentPartComponent {
  $$$
  processedContentBeforeReference$_
  $$$
}'

# Look for content processing method
ast-grep --pattern 'processContent($_) {
  $$$
}'

Length of output: 284


Script:

#!/bin/bash
# Let's try a different approach to find the component implementation
# First, find the component file
fd "posting-content-part.component.ts" 

# Then use ripgrep to look for the content processing logic
rg "processedContentBeforeReference" -A 5
rg "class PostingContentPartComponent" -A 10

Length of output: 4707


Script:

#!/bin/bash
# Let's look for the escapeNumberedList method implementation
rg "escapeNumberedList" -A 10

# Also check for the htmlForPostingMarkdown pipe implementation
fd "html-for-posting-markdown.pipe.ts"
rg "transform.*htmlForPostingMarkdown" -A 5

Length of output: 2874


Script:

#!/bin/bash
# Let's examine the content of the markdown pipe implementation
cat src/main/webapp/app/shared/pipes/html-for-posting-markdown.pipe.ts

# Also check if there are any related tests for this functionality
fd "posting-content-part.component.spec.ts"

Length of output: 1536

src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.components.ts (1)

38-39: Verify security implications of HTML sanitization.

The allowed HTML tags list has been modified. Please ensure that the Angular sanitizer is properly handling these tags to prevent XSS attacks.

✅ Verification successful

Let me check the specific implementation related to the HTML sanitization in the posting content component and related files.


Let me check the implementation of the htmlForPostingMarkdown pipe since it's being used to process the content with the allowed tags.


Let me check the implementation of ArtemisMarkdownService to see how it handles the sanitization.


Let me check the final piece - the markdown conversion util implementation since it's using DOMPurify for sanitization.


HTML sanitization is properly implemented and secure

The code implements a robust security approach for HTML sanitization:

  1. Uses DOMPurify for sanitization with explicit allowlist of HTML tags and attributes
  2. Applies Angular's DomSanitizer as an additional security layer
  3. Follows proper sanitization flow:
    • Markdown conversion → DOMPurify sanitization → Angular sanitizer
    • Custom tags/attributes are explicitly controlled via allowlists
    • No direct bypassing of security without sanitization

The modified HTML tags list is safe as it's properly enforced through DOMPurify's ALLOWED_TAGS configuration.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for DomSanitizer usage in the template and related files
rg -l "DomSanitizer|bypassSecurityTrustHtml" --type ts

Length of output: 696


Script:

#!/bin/bash
# Check the implementation of posting-content-part component and its template
rg -A 10 "allowedHtmlTags" src/main/webapp/app/shared/metis/posting-content/posting-content-part/

# Check the safe-html pipe implementation
cat src/main/webapp/app/shared/pipes/safe-html.pipe.ts

# Look for sanitization related code in the component
rg -A 5 "sanitize|sanitization|sanitizer" src/main/webapp/app/shared/metis/posting-content/posting-content-part/

Length of output: 4716


Script:

#!/bin/bash
# Check the implementation of htmlForPostingMarkdown pipe
rg -l "htmlForPostingMarkdown" --type ts

# Once found, let's see its implementation
cat src/main/webapp/app/shared/pipes/html-for-posting-markdown.pipe.ts

Length of output: 1497


Script:

#!/bin/bash
# Check the markdown service implementation
cat src/main/webapp/app/shared/markdown.service.ts

# Also check for any DOMPurify usage as it's a common sanitization library
rg -A 5 "DOMPurify" --type ts

Length of output: 6400


Script:

#!/bin/bash
# Check the markdown conversion util implementation
cat src/main/webapp/app/shared/util/markdown.conversion.util.ts

Length of output: 3743

src/main/webapp/app/shared/metis/posting-markdown-editor/posting-markdown-editor.component.ts (3)

42-44: LGTM: New action imports follow Angular conventions.

The imports for the new formatting actions are properly organized and follow the Angular style guide naming conventions.


Line range hint 65-69: LGTM: Well-documented input property using modern Angular syntax.

The activeConversation input is properly typed, immutable, and well-documented. The usage of the new input() syntax aligns with modern Angular practices.


103-106: Verify cleanup of action instances.

The new formatting actions are logically placed within the text formatting group. However, since these actions are instantiated in ngOnInit, please ensure they are properly cleaned up to prevent memory leaks.

Let's verify if the actions implement cleanup methods:

✅ Verification successful

No cleanup concerns - proper disposal is implemented

The review comment's concern about cleanup is addressed by the existing implementation:

  1. All actions inherit from TextEditorAction which implements Disposable interface with a proper dispose() method that cleans up editor registrations
  2. EmojiAction specifically has additional cleanup through destroyEmojiPicker() that handles its overlay component
  3. The actions are instantiated in ngOnInit and will be automatically disposed when the component is destroyed through Angular's lifecycle

The implementation follows proper cleanup patterns and no memory leaks are expected.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the new actions implement destroy/cleanup methods
ast-grep --pattern 'class $_(Action) {
  $$$
  destroy() {
    $$$
  }
  $$$
}'

Length of output: 81


Script:

#!/bin/bash
# Let's check the implementation of these specific action classes
rg -A 10 "class (Strikethrough|BulletedList|OrderedList)Action" 

# Also check the base class or interface they might implement
rg -A 5 "interface.*Action|class.*Action.*\{" 

Length of output: 339924


Script:

#!/bin/bash
# Let's check the base class TextEditorAction to see if it has cleanup/destroy methods
rg -A 10 "class TextEditorAction.*implements Disposable" 

# Also check if EmojiAction has any special cleanup
rg -A 10 "class EmojiAction"

Length of output: 2621


Script:

#!/bin/bash
# Let's check if EmojiAction has any cleanup/destroy methods
rg -B 5 -A 10 "destroy\(\)|dispose\(\)" src/main/webapp/app/shared/monaco-editor/model/actions/emoji.action.ts

# Also check the full implementation of TextEditorAction's dispose method
rg -B 5 -A 10 "dispose\(\)" src/main/webapp/app/shared/monaco-editor/model/actions/text-editor-action.model.ts

Length of output: 1153

src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action.integration.spec.ts (1)

95-95: Verify the double space requirement after ordered list marker.

The test now expects two spaces after the ordered list marker ('1. '). While this change aligns with some markdown specifications, we should verify if this is intentional and consistent with the application's markdown parser.

✅ Verification successful

Let me gather more context about the ordered list formatting implementation and tests.


Let me search for more context about the ordered list formatting in the codebase.


Let me search for the implementation of the ordered list formatting to understand if the double space is intentional.


Let me try one more search to find the ordered list action implementation.


Based on the implementation found in ordered-list.action.ts, I can now provide a final response:

The double space after ordered list marker is intentional and correct.

The implementation in OrderedListAction class explicitly handles spacing to ensure consistent alignment:

public getPrefix(lineNumber: number): string {
    const space = lineNumber >= 10 ? ' ' : '  ';
    return `${lineNumber}.${space}`;
}

The code adds two spaces for single-digit numbers (1-9) and one space for double-digit numbers (10+) to maintain consistent alignment of list content. This is a common markdown formatting practice to ensure proper rendering across different markdown parsers.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if the double space after ordered list marker is consistently used across the codebase
# Look for other test files that might be testing ordered list formatting
rg -g '*.spec.ts' -A 2 "expect\(.*\.getText\(\)\)\.toBe\('1\."

Length of output: 412


Script:

#!/bin/bash
# Check the implementation of ordered list formatting
ast-grep --pattern 'function $FUNC($$$) {
  $$$
  $TEXT = $$.replace(/^\d+\./g, $$$)
  $$$
}'

# Look for markdown-related configuration or constants
rg -g '*.ts' 'const.*LIST.*=.*1\.' -B2 -A2

# Check for any documentation about markdown formatting
fd README.md --exec cat {}

Length of output: 64619


Script:

#!/bin/bash
# Look for markdown formatting related files and configurations
rg -g '*.ts' -g '*.js' 'orderedList|ordered-list' -B2 -A2

# Check for any Monaco editor formatting related code
ast-grep --pattern 'function $FUNC($$$) {
  $$$
  text = $$.replace(/^\d+\./g, $$$)
  $$$
}'

# Look for test files related to Monaco editor formatting
rg -g '*.spec.ts' 'monaco.*format' -B2 -A2

Length of output: 25290


Script:

#!/bin/bash
# Look for the ordered list action implementation
ast-grep --pattern 'class $CLASS extends $BASE {
  $$$
  static readonly ID = "ordered-list.action"
  $$$
}'

# Search for list-related constants or configurations
rg -g '*.ts' 'LIST_.*=.*' -B2 -A2

# Look for markdown formatting related tests
rg -g '*.spec.ts' 'markdown.*format|format.*markdown' -B2 -A2

Length of output: 1426


Script:

#!/bin/bash
# Look for ordered list implementation
rg -g '*.ts' 'class OrderedListAction' -B5 -A20

# Look for list formatting related constants
rg -g '*.ts' 'const.*LIST.*=.*\d+\.' -B2 -A2

# Check for markdown formatting configuration
rg -g '*.ts' 'markdown.*config|format.*config' -B2 -A2

Length of output: 3437

src/main/webapp/app/shared/monaco-editor/monaco-editor.component.ts (1)

113-118: LGTM! Consider handling additional edge cases.

The implementation correctly addresses the requirement to only convert words starting with ':' to emojis. However, be aware of these edge cases:

  1. Multiple spaces between words might be collapsed
  2. Emojis within words (not space-separated) won't be converted
  3. Special whitespace characters (newlines, tabs) might not be preserved

Let's verify the edge cases:

src/test/javascript/spec/component/shared/metis/postings-markdown-editor/postings-markdown-editor.component.spec.ts (3)

32-38: LGTM! Well-structured imports and comprehensive mock implementation.

The new imports are well-organized, and the mock editor implementation is thorough, including all necessary methods for testing text editor functionality.

Also applies to: 62-80


107-109: LGTM! Proper test isolation with WeakMap cleanup.

The cleanup of ListAction listeners using WeakMap in beforeEach ensures proper test isolation and prevents test pollution.


378-490: LGTM! Comprehensive test coverage for list formatting features.

The test suite thoroughly covers:

  • Basic list formatting (bulleted and ordered)
  • List type switching
  • Prefix toggling
  • Edge cases with inline lists
  • Interaction between different list types

The tests are well-organized, follow the AAA pattern, and properly clean up between test cases.

src/main/webapp/app/shared/markdown-editor/monaco/markdown-editor-monaco.component.ts (3)

29-30: LGTM! Import changes align with new formatting features.

The new imports for BulletedListAction and StrikethroughAction are properly organized and follow Angular style guidelines.


163-163: LGTM! New formatting actions are properly integrated.

The new actions are logically placed in the defaultActions array:

  • StrikethroughAction is grouped with other text formatting actions
  • BulletedListAction is appropriately placed next to OrderedListAction

Also applies to: 170-170


163-163: Verify integration of new formatting features.

Please ensure that:

  1. The preview mode correctly renders the new strikethrough and bulleted list formatting
  2. The visual mode supports these new formatting options
  3. The markdown parser handles these new formats correctly

Also applies to: 170-170

✅ Verification successful

The previous search failed due to incorrect paths. Let's find the actual file locations and then check their implementations for handling these markdown features.


Let's verify if the markdown service and content components properly handle the rendering of these markdown features.


We need to verify how the markdown service is configured and how the HTML pipe processes the markdown content to ensure proper rendering support for these features.


We need to check the actual markdown conversion implementation to verify if strikethrough and bullet lists are properly configured in the markdown-it setup.


Based on the comprehensive analysis of the codebase, I can now provide the final response:

Strikethrough and bullet list formatting are properly supported

The implementation shows that:

  1. The markdown-it library is properly configured with HTML support enabled
  2. The content is rendered through the HtmlForPostingMarkdownPipe which uses ArtemisMarkdownService
  3. The markdown preview is displayed in elements with the markdown-preview class
  4. The content is properly sanitized while preserving HTML formatting

The new formatting actions are well-integrated with the existing markdown rendering pipeline and will work correctly in both preview and final display.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if preview/visual mode components handle new formatting
echo "Checking preview mode handling..."
rg -A 5 "strikethrough|bullet" "src/main/webapp/**/posting-content-part.component.*"

echo "Checking visual mode handling..."
rg -A 5 "strikethrough|bullet" "src/main/webapp/**/visual-editor.component.*"

echo "Checking markdown parser handling..."
rg -A 5 "strikethrough|bullet" "src/main/webapp/**/markdown.service.*"

Length of output: 729


Script:

#!/bin/bash
# Let's first find the correct paths for these files
echo "Finding relevant files..."
fd -e ts -e html "posting-content|visual-editor|markdown.service"

echo "Checking markdown rendering implementation..."
rg -A 5 "marked" --type ts

echo "Checking strikethrough configuration..."
rg -A 5 "strikethrough|bullet" --type ts

Length of output: 51273


Script:

#!/bin/bash
# Let's check the markdown service implementation for rendering support
echo "Checking markdown service implementation..."
rg -A 10 "marked\.setOptions|marked\.parse" src/main/webapp/app/shared/markdown.service.ts

# Check if there are any tests for markdown rendering
echo "Checking markdown rendering tests..."
rg -A 5 "strikethrough|bullet" src/test/javascript/spec/service/markdown.service.spec.ts

# Check if the posting content component handles markdown rendering
echo "Checking posting content component..."
rg -A 5 "markdown" src/main/webapp/app/shared/metis/posting-content/posting-content-part/posting-content-part.component.html

Length of output: 1455


Script:

#!/bin/bash
# Let's check the markdown service implementation directly
echo "Checking markdown service file content..."
cat src/main/webapp/app/shared/markdown.service.ts

# Check the HTML pipe that processes markdown
echo "Checking HTML pipe implementation..."
rg -A 10 "class HtmlForPostingMarkdownPipe" --type ts

Length of output: 4956


Script:

#!/bin/bash
# Let's check the markdown conversion utility implementation
echo "Checking markdown conversion utility..."
cat src/main/webapp/app/shared/util/markdown.conversion.util.ts

# Check if there are any markdown-it configuration files
echo "Checking for markdown-it configuration..."
fd -e ts "markdown-it|markdown.config"

Length of output: 3966

src/main/webapp/app/exercises/quiz/manage/short-answer-question/short-answer-question-edit.component.ts (1)

35-36: LGTM! New formatting actions imported.

The imports align with the PR objectives to add strikethrough and bulleted list formatting options.

src/main/webapp/app/shared/monaco-editor/model/actions/ordered-list.action.ts (3)

2-2: LGTM!

The import statement correctly imports ListAction to extend common list functionalities.


7-7: Confirm inheritance from ListAction

Changing the superclass to ListAction is appropriate if you're unifying list actions under a common parent. Ensure that all necessary functionalities are correctly inherited.


14-16: LGTM!

The getPrefix method correctly generates the numbered list prefix based on the line number.

Comment on lines +18 to +21
protected getPrefix(lineNumber: number): string {
void lineNumber;
return this.PREFIX;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve handling of unused lineNumber parameter

The lineNumber parameter is marked as unused with void. If the parameter isn't needed, consider:

  1. Removing it if not required by base class contract
  2. Implementing line number-based prefixing if it should affect the bullet style

If the parameter is truly unused, apply this change:

-    protected getPrefix(lineNumber: number): string {
-        void lineNumber;
-        return this.PREFIX;
-    }
+    protected getPrefix(): string {
+        return this.PREFIX;
+    }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +74 to +82
processContent() {
if (this.postingContentPart.contentBeforeReference) {
this.processedContentBeforeReference = this.escapeNumberedList(this.postingContentPart.contentBeforeReference);
}

if (this.postingContentPart.contentAfterReference) {
this.processedContentAfterReference = this.escapeNumberedList(this.postingContentPart.contentAfterReference);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add error handling in processContent method.

The method should handle cases where postingContentPart is undefined or null to prevent runtime errors.

     processContent() {
+        if (!this.postingContentPart) {
+            return;
+        }
+
         if (this.postingContentPart.contentBeforeReference) {
             this.processedContentBeforeReference = this.escapeNumberedList(this.postingContentPart.contentBeforeReference);
         }

         if (this.postingContentPart.contentAfterReference) {
             this.processedContentAfterReference = this.escapeNumberedList(this.postingContentPart.contentAfterReference);
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
processContent() {
if (this.postingContentPart.contentBeforeReference) {
this.processedContentBeforeReference = this.escapeNumberedList(this.postingContentPart.contentBeforeReference);
}
if (this.postingContentPart.contentAfterReference) {
this.processedContentAfterReference = this.escapeNumberedList(this.postingContentPart.contentAfterReference);
}
}
processContent() {
if (!this.postingContentPart) {
return;
}
if (this.postingContentPart.contentBeforeReference) {
this.processedContentBeforeReference = this.escapeNumberedList(this.postingContentPart.contentBeforeReference);
}
if (this.postingContentPart.contentAfterReference) {
this.processedContentAfterReference = this.escapeNumberedList(this.postingContentPart.contentAfterReference);
}
}

Comment on lines 94 to 96
action.executeInCurrentEditor();
expect(comp.getText()).toBe('1. ');
});

it('should toggle ordered list, skipping empty lines', () => {
const action = new OrderedListAction();
comp.registerAction(action);
const lines = ['One', '', 'Two', 'Three'];
const numberedLines = lines.map((line, index) => (line ? `${index + 1}. ${line}` : ''));
comp.setText(lines.join('\n'));
comp.setSelection({ startLineNumber: 1, startColumn: 1, endLineNumber: lines.length, endColumn: lines[lines.length - 1].length + 1 });
// Introduce list
action.executeInCurrentEditor();
expect(comp.getText()).toBe(numberedLines.join('\n'));
// Remove list
action.executeInCurrentEditor();
expect(comp.getText()).toBe(lines.join('\n'));
expect(comp.getText()).toBe('1. ');
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add test coverage for ordered list toggling.

The test suite is missing coverage for toggling ordered lists. Similar to the unordered list test at lines 40-54, we should add a test case to verify that ordered lists can be properly toggled while skipping empty lines.

Here's a suggested test case to add:

it('should toggle ordered list, skipping empty lines', () => {
    const action = new OrderedListAction();
    comp.registerAction(action);
    const lines = ['One', '', 'Two', 'Three'];
    const orderedLines = lines.map((line, index) => (line ? `${index + 1}. ${line}` : ''));
    comp.setText(lines.join('\n'));
    comp.setSelection({ 
        startLineNumber: 1, 
        startColumn: 1, 
        endLineNumber: lines.length, 
        endColumn: lines[lines.length - 1].length + 1 
    });
    // Introduce list
    action.executeInCurrentEditor();
    expect(comp.getText()).toBe(orderedLines.join('\n'));
    // Remove list
    action.executeInCurrentEditor();
    expect(comp.getText()).toBe(lines.join('\n'));
});

Comment on lines +126 to 130
new StrikethroughAction(),
new CodeAction(),
new UrlAction(),
new UnorderedListAction(),
new BulletedListAction(),
new OrderedListAction(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Remaining references to UnorderedListAction need to be updated

  • src/test/javascript/spec/component/shared/monaco-editor/monaco-editor-action.integration.spec.ts: Tests are still using UnorderedListAction
  • src/main/webapp/app/shared/monaco-editor/model/actions/unordered-list.action.ts: The old action file still exists and needs to be removed

The codebase still contains references to the old UnorderedListAction in test files and the original action file. These need to be updated to use BulletedListAction for consistency.

🔗 Analysis chain

Verify the replacement of UnorderedListAction with BulletedListAction.

The changes look good, but let's verify that all existing usages of UnorderedListAction have been updated to use BulletedListAction to prevent any broken functionality.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any remaining references to UnorderedListAction
rg "UnorderedListAction" -A 5

# Search for existing usages of BulletedListAction to ensure proper integration
rg "BulletedListAction" -A 5

Length of output: 20864

Comment on lines +39 to +46
const numberedListRegex = /^\s*\d+\.\s+/;
const bulletListRegex = /^\s*[-*+•]\s+/;

if (numberedListRegex.test(line)) {
return line.replace(numberedListRegex, '');
} else if (bulletListRegex.test(line)) {
return line.replace(bulletListRegex, '');
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

[Critical Issue] Correct the regular expressions to match list prefixes accurately

The regular expressions used to detect bullet list prefixes may incorrectly interpret the hyphen - as a range indicator within the character class, leading to unexpected matches. To avoid this issue, the hyphen should either be escaped or placed at the beginning or end of the character class.

Apply the following diff to fix the regular expressions:

In `stripAnyListPrefix` method (lines 39-46):
- const bulletListRegex = /^\s*[-*+•]\s+/;
+ const bulletListRegex = /^\s*[*+•-]\s+/;

In `hasPrefix` method (lines 132-135):
- const bulletListRegex = /^\s*[•\-*+]\s+/;
+ const bulletListRegex = /^\s*[*+•-]\s+/;

In `handleBackspace` method (lines 179-190):
- const linePrefixMatch = lineContent.match(/^\s*(\d+\.\s+|[-*+•]\s+)/);
+ const linePrefixMatch = lineContent.match(/^\s*(\d+\.\s+|[*+•-]\s+)/);

Also applies to: 132-135, 179-190

Comment on lines 55 to 67
run(editor: TextEditor) {
if (!ListAction.editorsWithListener.has(editor)) {
ListAction.editorsWithListener.set(editor, true);

editor.getDomNode()?.addEventListener('keydown', (event: KeyboardEvent) => {
if (event.key === 'Enter' && event.shiftKey) {
event.preventDefault();
this.handleShiftEnter(editor);
} else if (event.key === 'Backspace') {
this.handleBackspace(editor, event);
}
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

[Refactor Suggestion] Use editor's API to manage event listeners and prevent memory leaks

Adding event listeners directly to the editor's DOM node may lead to memory leaks if the editor is disposed and recreated, as the event listeners may persist. To prevent this and to ensure proper lifecycle management, consider using the editor's API methods such as addCommand or addAction to handle key events. This approach integrates with the editor's lifecycle and eliminates the need to track editors manually.

Copy link

@Feras797 Feras797 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested on TS1. The only issue I encountered is the ordered list not incrementing when new points are added. I manually selected the ordered list every time I started a new line (cmd + ↵). Apart from this everything works as described.
image

Copy link

@JanaNF JanaNF left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Manual tested on TS1.
Only issue: when you change the text to e.g. bold you can add on (un)order. I don't know if this is intended.
image

@asliayk
Copy link
Contributor Author

asliayk commented Nov 3, 2024

thank you for your reviews :) I've updated the code with these changes:

  • The editor now handles Cmd+Enter for adding new lines with related prefixes, just like it does with Shift+Enter.
  • If the cursor is in the middle of a line and a list option is chosen, the prefix is added at the start of the line, similar to how it works in Slack.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (4)
src/test/javascript/spec/component/shared/metis/postings-markdown-editor/postings-markdown-editor.component.spec.ts (4)

107-112: Consider improving type safety and test isolation.

The mock setup could be enhanced in two ways:

  1. Use a more type-safe approach for the event listener mock
  2. Move the WeakMap clearing to the beforeEach block for better test isolation
 beforeEach(() => {
+    // Clear the WeakMap at the start of each test
+    (ListAction as any).editorsWithListener = new WeakMap<TextEditor, boolean>();
+
     mockEditor.getDomNode.mockReturnValue({
-        addEventListener: jest.fn(),
+        addEventListener: jest.fn<void, [string, (event: KeyboardEvent) => void]>(),
     } as any);

     jest.clearAllMocks();
-    (ListAction as any).editorsWithListener = new WeakMap<TextEditor, boolean>();
 });

339-429: Add JSDoc documentation to helper functions.

The helper functions are well-implemented but would benefit from documentation to improve maintainability.

+/**
+ * Simulates a keydown event on the editor and returns the event and spy objects.
+ * @param editor - The text editor instance
+ * @param key - The key to simulate
+ * @param modifiers - Optional keyboard modifiers (shift, meta)
+ * @returns Object containing the event and spy objects
+ */
 const simulateKeydownEvent = (editor: TextEditor, key: string, modifiers: { shiftKey?: boolean; metaKey?: boolean } = {}) => {
     // ... existing implementation
 };

+/**
+ * Simulates a list action on the editor and verifies the expected text transformation.
+ * @param action - The text editor action to simulate
+ * @param selectedText - The initial selected text
+ * @param expectedText - The expected text after transformation
+ * @param startLineNumber - Optional starting line number (defaults to 1)
+ */
 const simulateListAction = (action: TextEditorAction, selectedText: string, expectedText: string, startLineNumber: number = 1) => {
     // ... existing implementation
 };

431-543: Consider adding edge cases and improving test organization.

While the test coverage is comprehensive for basic list formatting, consider:

  1. Adding tests for edge cases:
    • Empty lines within lists
    • Mixed content (lists with non-list items)
    • Lists with different indentation levels
  2. Organizing tests using describe blocks for better readability
+describe('List Formatting', () => {
+  describe('Bulleted Lists', () => {
     it('should add bulleted list prefixes correctly', () => {
       // ... existing test
     });
     
     it('should remove bulleted list prefixes correctly when toggled', () => {
       // ... existing test
     });
+
+    it('should handle empty lines within bulleted lists', () => {
+      const bulletedListAction = component.defaultActions.find((action: any) => action instanceof BulletedListAction) as BulletedListAction;
+      const selectedText = `First line\n\nThird line`;
+      const expectedText = `•  First line\n\n•  Third line`;
+      
+      simulateListAction(bulletedListAction, selectedText, expectedText);
+    });
   });

+  describe('Ordered Lists', () => {
     // ... existing ordered list tests
   });

+  describe('List Type Switching', () => {
     // ... existing list switching tests
   });
 });

351-367: Consider testing backspace behavior in list formatting.

While the Enter key handling is well-tested, consider adding tests for backspace behavior at the start of list items, which is a common edge case in list formatting.

it('should handle backspace at start of list item correctly', () => {
    const bulletedListAction = component.defaultActions.find((action) => action instanceof BulletedListAction) as BulletedListAction;
    
    mockEditor.getPosition.mockReturnValue({
        getLineNumber: () => 2,
        getColumn: () => 3,
    } as TextEditorPosition);
    mockEditor.getLineText.mockReturnValue('•  Second line');
    
    bulletedListAction.run(mockEditor);
    
    const { preventDefaultSpy } = simulateKeydownEvent(mockEditor, 'Backspace');
    
    expect(preventDefaultSpy).toHaveBeenCalled();
    expect(mockEditor.replaceTextAtRange).toHaveBeenCalledWith(
        expect.any(TextEditorRange),
        'Second line'
    );
});
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c702918 and 48d7fc1.

📒 Files selected for processing (2)
  • src/main/webapp/app/shared/monaco-editor/model/actions/list.action.ts (1 hunks)
  • src/test/javascript/spec/component/shared/metis/postings-markdown-editor/postings-markdown-editor.component.spec.ts (5 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/webapp/app/shared/monaco-editor/model/actions/list.action.ts
🧰 Additional context used
📓 Path-based instructions (1)
src/test/javascript/spec/component/shared/metis/postings-markdown-editor/postings-markdown-editor.component.spec.ts (1)

Pattern src/test/javascript/spec/**/*.ts: jest: true; mock: NgMocks; bad_practices: avoid_full_module_import; perf_improvements: mock_irrelevant_deps; service_testing: mock_http_for_logic; no_schema: avoid_NO_ERRORS_SCHEMA; expectation_specificity: true; solutions: {boolean: toBeTrue/False, reference: toBe, existence: toBeNull/NotNull, undefined: toBeUndefined, class_obj: toContainEntries/toEqual, spy_calls: {not_called: not.toHaveBeenCalled, once: toHaveBeenCalledOnce, with_value: toHaveBeenCalledWith|toHaveBeenCalledExactlyOnceWith}}

Copy link

@sachmii sachmii left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on TS2, worked just fine.

Bildschirmfoto 2024-11-04 um 12 43 27

@HawKhiem HawKhiem temporarily deployed to artemis-test4.artemis.cit.tum.de November 4, 2024 21:19 — with GitHub Actions Inactive
@krusche krusche added this to the 7.7.0 milestone Nov 7, 2024
@krusche krusche merged commit a8ec277 into develop Nov 7, 2024
58 of 64 checks passed
@krusche krusche deleted the feature/communication/add-more-input-formatting-options branch November 7, 2024 23:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
client Pull requests that update TypeScript code. (Added Automatically!) component:Communication ready to merge tests
Projects
Archived in project
Development

Successfully merging this pull request may close these issues.

8 participants