From fd4c1a205db2fb455fbcdd61ba29b211253a3c77 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Janis=20Da=CC=88hne?= <janis.daehne@informatik.uni-halle.de>
Date: Tue, 1 Nov 2022 17:01:00 +0100
Subject: [PATCH] - added support for language specific exercise description
 blocks - releases list now shows programming language

---
 package.json                                  |  1 +
 .../markdownRenderer.tsx                      | 36 ++++++++++++++++++-
 .../renderContentComponents/renderWrapper.tsx |  6 ++++
 .../sites/doExerciseSite/doExerciseSite.tsx   |  2 ++
 .../editCustomProjectSite.tsx                 |  1 +
 .../exerciseEditorSite/exerciseEditorSite.tsx |  1 +
 .../exerciseSyntaxHelpPanelView.tsx           |  2 +-
 .../previewPanel/previewPanelView.tsx         |  7 ++++
 .../releasesSite/listView.tsx                 | 31 +++++++++++++++-
 .../sites/tutorViewSite/tutorViewSite.tsx     |  2 ++
 src/constants.ts                              |  5 ++-
 src/helpers/markdownHelper.ts                 | 19 ++++++++++
 .../exerciseQuestionSyntaxGuide.md            | 22 ++++++++++++
 src/styles/common.styl                        |  3 ++
 yarn.lock                                     |  5 +++
 15 files changed, 139 insertions(+), 4 deletions(-)

diff --git a/package.json b/package.json
index 7c31e6f5..ed6398c0 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
     "lodash": "4.17.10",
     "lodash-es": "4.17.10",
     "markdown-it": "8.4.2",
+    "markdown-it-container": "3.0.0",
     "markdown-it-mathjax": "2.0.0",
     "mathjax": "2.7.x",
     "moment": "2.22.2",
diff --git a/src/components/renderContentComponents/markdownRenderer.tsx b/src/components/renderContentComponents/markdownRenderer.tsx
index b116c768..bf875c58 100644
--- a/src/components/renderContentComponents/markdownRenderer.tsx
+++ b/src/components/renderContentComponents/markdownRenderer.tsx
@@ -14,6 +14,7 @@ import Logger from "../../helpers/logger";
 
 //because we need mathjax support support in markdown we changed the parser a bit...
 import markdown from '../../helpers/markdownHelper'
+import { markdownPLangBlockDataLangAttribute, markdownPLangBlocksClass } from "../../constants";
 
 //const css = require('./styles.styl');
 
@@ -23,7 +24,8 @@ const mapStateToProps = (rootState: RootState , props: MyProps) => {
   return {
     //test0: rootState...
     //test: props.test
-    ...props
+    ...props,
+    pLangs: rootState.pLangsState.pLangs,
   }
 }
 
@@ -54,6 +56,7 @@ class markdownRenderer extends React.Component<Props, any> {
       //this can happen if the user opens a dialog --> this will be unmounted
       //but dummy won't change anymore (only set to false once when everything is loaded & assets created)
       this.refreshMath();
+      this.removeBlocksForWrongPLang();
     }
 
   }
@@ -62,6 +65,7 @@ class markdownRenderer extends React.Component<Props, any> {
     //see componentDidMount for explanation
     if (this.props.content && this.props.dummy === false) {
       this.refreshMath()
+      this.removeBlocksForWrongPLang();
     }
   }
 
@@ -74,6 +78,36 @@ class markdownRenderer extends React.Component<Props, any> {
     }
   }
 
+  removeBlocksForWrongPLang(): void {
+
+    const plang = this.props.pLangs.find(p => p.id === this.props.plangId)
+
+    const allPLangBlocks = Array.from(document.querySelectorAll(`.${markdownPLangBlocksClass}[${markdownPLangBlockDataLangAttribute}]`))
+
+    for (let i = 0; i < allPLangBlocks.length; i++) {
+      const divBlock = allPLangBlocks[i];
+
+      const langAttribute = divBlock.getAttribute(`${markdownPLangBlockDataLangAttribute}`)
+
+      if (!langAttribute) {
+        divBlock.remove()
+
+      } else {
+        //we have an attribute, check if the block is for the specified lang
+        
+        //no filter or correct lang
+        if (!plang || plang.displayName.trim() === langAttribute.trim()) {
+          //ok
+          continue
+        }
+
+        //else remove
+        divBlock.remove()
+      }
+    }
+
+  }
+
   render(): JSX.Element {
 
 
diff --git a/src/components/renderContentComponents/renderWrapper.tsx b/src/components/renderContentComponents/renderWrapper.tsx
index b8b5a31e..61b4e3ab 100644
--- a/src/components/renderContentComponents/renderWrapper.tsx
+++ b/src/components/renderContentComponents/renderWrapper.tsx
@@ -33,6 +33,12 @@ export interface MyProps {
    * id used for printing
    */
   readonly printId: string
+
+  /**
+   * some (markdown) blocks might be only for specific languages,
+   * use null to show them all
+   */
+  readonly plangId: number | null
 }
 
 const mapStateToProps = (rootState: RootState, props: MyProps) => {
diff --git a/src/components/sites/doExerciseSite/doExerciseSite.tsx b/src/components/sites/doExerciseSite/doExerciseSite.tsx
index fa54260b..c0d4afaa 100644
--- a/src/components/sites/doExerciseSite/doExerciseSite.tsx
+++ b/src/components/sites/doExerciseSite/doExerciseSite.tsx
@@ -1185,6 +1185,7 @@ class doExerciseSite extends React.Component<Props & RouteComponentProps<Matched
                                     assets={this.props.doExercise.exerciseDescription.assets}
                                     dummy={this.props.dummy}
                                     fontSizeInPx={this.props.taskDescriptionFontSizeInPx}
+                                    plangId={this.props.solutionPLangId}
                   />
                 </TabContentHost>
 
@@ -1207,6 +1208,7 @@ class doExerciseSite extends React.Component<Props & RouteComponentProps<Matched
                       contentType={QuestionType.markdown}
                       dummy={this.props.dummy}
                       fontSizeInPx={this.props.taskDescriptionFontSizeInPx}
+                      plangId={null}
                     />
                   </TabContentHost>
                 }
diff --git a/src/components/sites/editCustomProjectSite/editCustomProjectSite.tsx b/src/components/sites/editCustomProjectSite/editCustomProjectSite.tsx
index 6e803761..20bbaeb6 100644
--- a/src/components/sites/editCustomProjectSite/editCustomProjectSite.tsx
+++ b/src/components/sites/editCustomProjectSite/editCustomProjectSite.tsx
@@ -534,6 +534,7 @@ class editCustomProjectSite extends React.Component<Props & RouteComponentProps<
                                     assets={this.props.customProjectDescription.assets}
                                     dummy={false /*not needed because we have tabs that will re render if tab index changed*/}
                                     fontSizeInPx={this.props.taskDescriptionFontSizeInPx}
+                                    plangId={null}
                   />
                 </TabContentHost>
 
diff --git a/src/components/sites/exerciseEditorSite/exerciseEditorSite.tsx b/src/components/sites/exerciseEditorSite/exerciseEditorSite.tsx
index 377627e4..b8215d48 100644
--- a/src/components/sites/exerciseEditorSite/exerciseEditorSite.tsx
+++ b/src/components/sites/exerciseEditorSite/exerciseEditorSite.tsx
@@ -495,6 +495,7 @@ class exerciseEditorSite extends React.Component<Props & RouteComponentProps<Mat
                                     assets={this.props.editorExercise.exerciseDescription.assets}
                                     dummy={false /*not needed because we have tabs that will re render if tab index changed*/}
                                     fontSizeInPx={this.props.taskDescriptionFontSizeInPx}
+                                    plangId={null}
                   />
                 </TabContentHost>
 
diff --git a/src/components/sites/exerciseEditorSite/exerciseSyntaxHelpPanel/exerciseSyntaxHelpPanelView.tsx b/src/components/sites/exerciseEditorSite/exerciseSyntaxHelpPanel/exerciseSyntaxHelpPanelView.tsx
index 58ebafe5..7385d91d 100644
--- a/src/components/sites/exerciseEditorSite/exerciseSyntaxHelpPanel/exerciseSyntaxHelpPanelView.tsx
+++ b/src/components/sites/exerciseEditorSite/exerciseSyntaxHelpPanel/exerciseSyntaxHelpPanelView.tsx
@@ -43,7 +43,7 @@ class exerciseSyntaxHelpPanelView extends React.Component<Props, any> {
     }
 
     return (
-      <div className="fh">
+      <div className="fh markdown-guide">
         <div dangerouslySetInnerHTML={content}>
 
         </div>
diff --git a/src/components/sites/exerciseEditorSite/previewPanel/previewPanelView.tsx b/src/components/sites/exerciseEditorSite/previewPanel/previewPanelView.tsx
index 5cd85a4f..56a785cd 100644
--- a/src/components/sites/exerciseEditorSite/previewPanel/previewPanelView.tsx
+++ b/src/components/sites/exerciseEditorSite/previewPanel/previewPanelView.tsx
@@ -29,6 +29,12 @@ export interface MyProps {
   readonly fontSizeInPx: number
 
   readonly printId: string
+
+  /**
+   * some (markdown) blocks might be only for specific languages,
+   * use null to show them all
+   */
+  readonly plangId: number | null
 }
 
 const mapStateToProps = (rootState: RootState, props: MyProps) => {
@@ -87,6 +93,7 @@ class PreviewPanelView extends React.Component<Props, any> {
             assets={this.props.assets}
             dummy={this.props.dummy}
             fontSizeInPx={this.props.fontSizeInPx}
+            plangId={this.props.plangId}
           />
         }
 
diff --git a/src/components/sites/manageOwnOrGroupExerciseComponents/releasesSite/listView.tsx b/src/components/sites/manageOwnOrGroupExerciseComponents/releasesSite/listView.tsx
index b6cf376f..b8dc7c2b 100644
--- a/src/components/sites/manageOwnOrGroupExerciseComponents/releasesSite/listView.tsx
+++ b/src/components/sites/manageOwnOrGroupExerciseComponents/releasesSite/listView.tsx
@@ -29,6 +29,7 @@ import {MyPopup} from "../../../helpers/myPopup";
 import {getI18n} from "../../../../../i18n/i18nRoot";
 import PaginationTableFooter from '../../../helpers/paginationTableFooter'
 import {ReleaseDurationType} from '../../../../types/ReleaseDurationType'
+import {ExercisePreviewFromBackend} from '../../../../types/exercisePreview'
 
 //const css = require('./styles.styl');
 
@@ -50,7 +51,8 @@ const mapStateToProps = (rootState: RootState /*, props: MyProps*/) => {
     pagination: rootState.releaseSiteState.pagination,
 
     groupRole: rootState.releaseSiteState.groupRole,
-    langId: rootState.i18nState.langId
+    langId: rootState.i18nState.langId,
+    pLangs: rootState.pLangsState.pLangs,
   }
 }
 
@@ -217,7 +219,24 @@ class ListView extends React.Component<Props, any> {
               </Table.HeaderCell>
 
               <Table.HeaderCell>
+                <span className="clickable"
+                      onClick={() => {
+                        this.props.setSortByKey('pLangId',
+                          rotateSorting<ExerciseReleaseFromBackend>(this.props.sortDirection,
+                            this.props.sortByKey,
+                            'pLangId',
+                          )
+                        )
+                      }}
+                >
+                  {
+                    <HelpPopup icon="puzzle piece"
+                               defaultText={getI18n(this.props.langId,'Programming language')}/>
+                  }
+                </span>
+              </Table.HeaderCell>
 
+              <Table.HeaderCell>
                 <span>
                   {
                     getI18n(this.props.langId, 'Miscellaneous')
@@ -291,6 +310,7 @@ class ListView extends React.Component<Props, any> {
             {
               list.map((exerciseRelease, index) => {
 
+                let programmingLanguageForRelease = this.props.pLangs.find(p => p.id === exerciseRelease.pLangId)
 
                 // tslint:disable-next-line
                 let editNode = null
@@ -445,6 +465,15 @@ class ListView extends React.Component<Props, any> {
 
                     </Table.Cell>
 
+                    <Table.Cell>
+                      {
+                        programmingLanguageForRelease &&
+                        <span>
+                          { programmingLanguageForRelease.displayName }
+                        </span>
+                      }
+                    </Table.Cell>
+
                     <Table.Cell>
 
                       <div>
diff --git a/src/components/sites/tutorViewSite/tutorViewSite.tsx b/src/components/sites/tutorViewSite/tutorViewSite.tsx
index 741556a4..8cb9c000 100644
--- a/src/components/sites/tutorViewSite/tutorViewSite.tsx
+++ b/src/components/sites/tutorViewSite/tutorViewSite.tsx
@@ -451,6 +451,7 @@ class tutorViewSite extends React.Component<Props & RouteComponentProps<MatchedP
                                     assets={this.props.exercise.exerciseDescription.assets}
                                     dummy={this.props.dummy}
                                     fontSizeInPx={this.props.taskDescriptionFontSizeInPx}
+                                    plangId={null}
                   />
 
                 </TabContentHost>
@@ -497,6 +498,7 @@ class tutorViewSite extends React.Component<Props & RouteComponentProps<MatchedP
                         contentType={QuestionType.markdown}
                         dummy={this.props.dummy}
                         fontSizeInPx={this.props.taskDescriptionFontSizeInPx}
+                        plangId={null}
                       />
                     </div>
                   }
diff --git a/src/constants.ts b/src/constants.ts
index 30d52343..fa0a7369 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -13,7 +13,7 @@ import Logger from './helpers/logger'
  * y - breaking changes / new features
  * z - fixes, small changes
  */
-export const versionString = '2.18.0'
+export const versionString = '2.18.1'
 
 
 export const supportMail = 'yapex@informatik.uni-halle.de'
@@ -404,3 +404,6 @@ export const dashboardRefreshIntervalInS = 5
 
 //used for all tests
 export const compileTimeoutDefaultsInMs = 3000
+
+export const markdownPLangBlocksClass = `plang-part`
+export const markdownPLangBlockDataLangAttribute = `data-programming-lang`
\ No newline at end of file
diff --git a/src/helpers/markdownHelper.ts b/src/helpers/markdownHelper.ts
index bbb50f1d..395ac272 100644
--- a/src/helpers/markdownHelper.ts
+++ b/src/helpers/markdownHelper.ts
@@ -2,6 +2,7 @@ import * as markdownIt from 'markdown-it'
 import {insertLineNumbers} from './stringHelper'
 import Logger from './logger'
 import {copyToClipboard} from './clipboard'
+import { markdownPLangBlockDataLangAttribute, markdownPLangBlocksClass } from '../constants'
 
 const hljs = require('highlight.js/lib/highlight')
 
@@ -257,6 +258,24 @@ const mdRenderer = markdownIt({
                                 }
                               })
   .use(require('markdown-it-mathjax')())
+  .use(require('markdown-it-container'), 'exercise-lang-block', { //by default div class="exercise-lang-block" is rendered
+    validate: (params: string) => {
+      return params.trim().match(/lang\s+[\w ]+/)
+    },
+    render: (tokens: ReadonlyArray<any>, idx: number) => {
+      let m = tokens[idx].info.trim().substr(`lang`.length).trim().match(/^[\w ]+$/);
+
+      if (tokens[idx].nesting === 1) {
+        // opening tag
+        
+        //div class="..." with data attribute to identify lang
+        return `<div class="${markdownPLangBlocksClass}" ${markdownPLangBlockDataLangAttribute}="${mdRenderer.utils.escapeHtml(m[0])}">\n`;
+      } else {
+        // closing tag
+        return '</div>\n';
+      }
+    }
+  })
 
 //https://github.com/markdown-it/markdown-it/blob/master/docs/architecture.md
 const oldLinkRule = function (tokens: any, idx: any, options: any, env: any, self: any): any {
diff --git a/src/questionSystem/exerciseQuestionSyntaxGuide.md b/src/questionSystem/exerciseQuestionSyntaxGuide.md
index 99e3109a..50824f4d 100644
--- a/src/questionSystem/exerciseQuestionSyntaxGuide.md
+++ b/src/questionSystem/exerciseQuestionSyntaxGuide.md
@@ -66,6 +66,28 @@ class Main {
 
 Für eine List mit Sprachen siehe Abschnitt `Syntax highlighting unterstützte Sprachen` (ganz unten) 
 
+### Programmiersprachenspezifische Anmerkungen
+
+Für Anmerkungen/Markdown, welche nur für eine spezielle Programmiersprache angezeigt werden sollen, kann man folgendermaßen vorgehen:
+
+```
+::: lang [Programmiersprache, wie sie in der Oberfläche angezeigt wird (nicht interner Name)]
+:::
+```
+
+also z.b.
+
+```
+::: lang Java
+Dieser Hinweis wird **nur** für `Java` angezeigt.
+:::
+```
+
+in diesen Blöcken kann wieder Markdown verwendet werden.
+
+Die Lister der Programmiersprachen hängt von der Konfiguration ab.
+Diese Liste ist z.B. beim freigeben einer Aufgabe zu finden oder beim Erstellen von eigenen Projekten.
+
 
 * ### Pdf (pdf)
 Es kann **eine** Url zu einem Pdf angegeben werden. Die Url muss dabei eine Asset-Url sein (beginnt mit asset://).
diff --git a/src/styles/common.styl b/src/styles/common.styl
index 42185cf2..794cfc9e 100644
--- a/src/styles/common.styl
+++ b/src/styles/common.styl
@@ -1612,3 +1612,6 @@ $sonarEase = linear
 }
 
 
+.markdown-guide pre {
+  overflow: auto;
+}
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index 7d263cd6..cc4e299b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3722,6 +3722,11 @@ map-visit@^1.0.0:
   dependencies:
     object-visit "^1.0.0"
 
+markdown-it-container@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/markdown-it-container/-/markdown-it-container-3.0.0.tgz#1d19b06040a020f9a827577bb7dbf67aa5de9a5b"
+  integrity sha512-y6oKTq4BB9OQuY/KLfk/O3ysFhB3IMYoIWhGJEidXt1NQFocFK2sA2t0NYZAMyMShAGL6x5OPIbrmXPIqaN9rw==
+
 markdown-it-mathjax@2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/markdown-it-mathjax/-/markdown-it-mathjax-2.0.0.tgz#ae2b4f4c5c719a03f9e475c664f7b2685231d9e9"
-- 
GitLab