Compare commits

...

63 Commits

Author SHA1 Message Date
Gerhard Beck a20b17699a Merge pull request 'Update Readme for license notices' (#135) from monofox/base:feature/ticket_90 into dev 2 months ago
Lukas Schreiner 6801462aad Update Readme for license notices 2 months ago
Gerhard Beck 14cc9c1e65 Merge pull request 'Extend Markdown Editor with Toolbar' (#134) from monofox/base:feature/ticket_90 into dev 2 months ago
Lukas Schreiner 598dede8b1 Extend Markdown Editor with Toolbar 2 months ago
pw 3739a42f8f Merge pull request 'Templates: Improved handling to support other modules' (#133) from monofox/base:feature/ticket_90 into dev 2 months ago
Lukas Schreiner be59ea0139 Transformed to new template ID 2 months ago
Lukas Schreiner dfc5681a05 Improved handling to support other modules. 2 months ago
pw 26b0d68a2f Merge pull request 'Hardening Templates and Plaintext-Mails' (#131) from monofox/base:feature/ticket_90 into dev 2 months ago
Lukas Schreiner 721a5af664 Update translations from Weblate 3 months ago
Gerhard Beck 24c73f7cfe Merge pull request 'Adding translation status to README' (#132) from feature/ticket_117 into dev 3 months ago
Lukas Schreiner c39e7e24be Adding translation status to README 3 months ago
Lukas Schreiner abd382da25 Fix HTML -> Text 3 months ago
Lukas Schreiner 99feea3eb1 Fix pipeline: templates in sep. folder 3 months ago
pw 201b156d2b Merge pull request 'Maintain Templates and sending of Welcome Mail' (#130) from monofox/base:feature/ticket_81 into dev 3 months ago
Lukas Schreiner efb122f808 Completed implementation of #81 + #90 3 months ago
Lukas Schreiner 21a83dacb9 Implementing all templates + Welcome mail. 3 months ago
Lukas Schreiner 9e1434c403 Implementing default templates as local markdown for better versioning. 3 months ago
Lukas Schreiner 630fcbc6e4 Template-Editor with Markdown support 3 months ago
Lukas Schreiner bf0b86daec Administration view of temlate management 3 months ago
Lukas Schreiner 940de0840e Allow to change modal dialog accept button 3 months ago
Lukas Schreiner 05a9b1744e First draft templates administration. 3 months ago
Lukas Schreiner 344e07d2a2 Restructure work directory 3 months ago
Lukas Schreiner eb9be4c3e9 New APIs for controller 3 months ago
Lukas Schreiner 986895e934 Further mail added (register -> admin) 3 months ago
Lukas Schreiner bee5e29e0e Cleanup templates fetch 3 months ago
Lukas Schreiner 47bc6de4f4 Fix retrieving tpl from MongoDB 3 months ago
Lukas Schreiner d0d0d76839 Template-Handling for Mails introduced 3 months ago
Lukas Schreiner 2f7183d39d Merge remote-tracking branch 'upstream/dev' into dev 3 months ago
Lukas Schreiner 56e9c51088 Revert "Merge remote-tracking branch 'upstream/main' into dev" 3 months ago
Lukas Schreiner 9fdfef07d3 Merge remote-tracking branch 'upstream/main' into dev 3 months ago
pw 2d50c41c52 Return to prev. screen after login (#126) 3 months ago
Lukas Schreiner 42d65e7795 Return to prev. screen after login 3 months ago
pw 88bcbb7d51 Dropdown menu is kept within boundaries (#125) 3 months ago
Lukas Schreiner 9f60b33008 Dropdown menu is kept within boundaries 3 months ago
pw 35f68d7686 Merge pull request 'Ignore production config and make sure it cannot be committed again' (#110) from Ryuno-Ki/base:gitignore into dev 3 months ago
pw a000a37ea7 Merge pull request 'Hyphenate long texts in user list cell' (#124) from monofox/base:feature/ticket_115 into dev 3 months ago
pw 080e499dd3 Merge pull request 'Access Token is created with user login name' (#123) from monofox/base:feature/ticket_122 into dev 3 months ago
pw cd97eec251 Merge pull request 'gitignore-main' (#114) from Ryuno-Ki/base:gitignore-main into main 3 months ago
pw af84ccba74 Merge pull request 'Avoid public access to protected pages' (#121) from monofox/base:feature/ticket_120 into dev 3 months ago
Lukas Schreiner bc0e1876c8 Hyphenate long texts in user list cell 3 months ago
Lukas Schreiner 649fd5814f Access Token is created with user login name 3 months ago
Lukas Schreiner 5373bd03d9 Avoid public access to protected pages 3 months ago
Gerhard Beck 30e31a7866 Merge pull request 'Refs #117: Centralize user settings changes' (#119) from monofox/base:dev into dev 3 months ago
Lukas Schreiner 5dfaeceb38 Removed debug messages 3 months ago
Lukas Schreiner 8f3833a4bd Refs #117: Centralize user settings changes 3 months ago
Gerhard Beck d7226a4b0e „CONTRIBUTING.md“ ändern 3 months ago
Gerhard Beck b5d3cc0fdd Merge pull request 'Descript how people can contribute to this project' (#116) from duco/lerntools_base:duco-patch-1 into main 3 months ago
pw ff0e347a19 Merge pull request 'lerntools/base#117: Language change on Login in root context' (#118) from monofox/base:dev into dev 3 months ago
Lukas Schreiner 87481dbbe6 lerntools/base#117: Main page text translation 3 months ago
Lukas Schreiner 7aaafd27f9 lerntools/base#117: Ensure, the language is set in root as well! 3 months ago
duco 158b7033f8 Mention to use the dev branch as merge target (+ Typo) 3 months ago
duco c7f60fd6d6 Added link to the wiki 3 months ago
duco 197d866da3 Descript how people can contribute to this project 3 months ago
Gerhard Beck 6405d1f381 Merge pull request 'Refs lerntools/base#93: Extracted translations into separate json files.' (#108) from monofox/base:dev into dev 3 months ago
André Jaenisch d9091d79ab
Downgrade dependencies to please Webpack 4 3 months ago
André Jaenisch 772220ba7c
Ignore production config and make sure it cannot be committed again 3 months ago
Lukas Schreiner 48572713dd Retrigger to prove CI is not doing anything anymore. 3 months ago
Lukas Schreiner ed316c5ee5 Retrigger build job 3 months ago
Gerhard Beck f98e1cd5cd Merge pull request 'Revert "Install and configure linters (but not enforce their usage just yet)"' (#112) from Ryuno-Ki/base:eslint-backout into dev 3 months ago
André Jaenisch 40cf8a8e69
Revert "Install and configure linters (but not enforce their usage just yet)" 3 months ago
André Jaenisch f628021393
Ignore production config and make sure it cannot be committed again 3 months ago
pw 9cff13aa6d Merge pull request '„package.json“ ändern' (#107) from dev into main 3 months ago
Lukas Schreiner c23d697a4c Refs lerntools/base#93: Extracted translations into separate json files. 3 months ago
  1. 15
      .eslintrc.js
  2. 4
      .gitignore
  3. 3
      .stylelintrc.json
  4. 11
      README.md
  5. 1
      app.config.example.js
  6. 31
      app.config.production.js
  7. 6
      app.js
  8. 21
      bin/exportDefaultTemplates.js
  9. 52
      bin/extractLocales.py
  10. 16
      bin/installDatabase.js
  11. 2
      bin/serverDev.sh
  12. 69
      main/components/ABCDList.vue
  13. 29
      main/components/AdminList.vue
  14. 11
      main/components/EmojiSelect.vue
  15. 15
      main/components/FileUpload.vue
  16. 69
      main/components/IndexList.vue
  17. 120
      main/components/MarkdownEditor.vue
  18. 7
      main/components/MenuHuge.vue
  19. 11
      main/components/MenuLarge.vue
  20. 11
      main/components/MenuSmall.vue
  21. 7
      main/components/MenuSmallEntry.vue
  22. 11
      main/components/ModalConfirm.vue
  23. 17
      main/components/ModalConfirmCancel.vue
  24. 31
      main/components/ModalDialog.vue
  25. 13
      main/components/ModalInfo.vue
  26. 13
      main/components/ModalInputTan.vue
  27. 24
      main/components/ModalSettings.vue
  28. 19
      main/components/ModalShare.vue
  29. 15
      main/components/NavbarBottom.vue
  30. 31
      main/components/NavbarTop.vue
  31. 42
      main/components/PageTitle-old.vue
  32. 17
      main/components/PageTitle.vue
  33. 13
      main/components/SearchBox.vue
  34. 123
      main/components/TemplateEditor.vue
  35. 11
      main/components/TexBlock.vue
  36. 7
      main/components/TooltipItem.vue
  37. 11
      main/components/WaitIcon.vue
  38. 87
      main/components/WarningList.vue
  39. 46
      main/config.js
  40. 100
      main/css/main.css
  41. 203
      main/js/markdown-editor.js
  42. 10
      main/js/markdown.js
  43. 21
      main/js/user-settings.js
  44. 66
      main/locales/ABCDList.json
  45. 22
      main/locales/Admin.json
  46. 26
      main/locales/AdminList.json
  47. 8
      main/locales/EmojiSelect.json
  48. 12
      main/locales/Error.json
  49. 12
      main/locales/FileUpload.json
  50. 12
      main/locales/Forbidden.json
  51. 8
      main/locales/Home.json
  52. 66
      main/locales/IndexList.json
  53. 18
      main/locales/Login.json
  54. 4
      main/locales/Logout.json
  55. 4
      main/locales/MenuHuge.json
  56. 8
      main/locales/MenuLarge.json
  57. 8
      main/locales/MenuSmall.json
  58. 4
      main/locales/MenuSmallEntry.json
  59. 8
      main/locales/ModalConfirm.json
  60. 14
      main/locales/ModalConfirmCancel.json
  61. 12
      main/locales/ModalDialog.json
  62. 10
      main/locales/ModalInfo.json
  63. 10
      main/locales/ModalInputTan.json
  64. 16
      main/locales/ModalSettings.json
  65. 16
      main/locales/ModalShare.json
  66. 12
      main/locales/NavbarBottom.json
  67. 26
      main/locales/NavbarTop.json
  68. 12
      main/locales/Notfound.json
  69. 4
      main/locales/PageTitle.json
  70. 40
      main/locales/Profile.json
  71. 10
      main/locales/RegisterConfirm.json
  72. 58
      main/locales/RegisterRequest.json
  73. 20
      main/locales/ResetConfirm.json
  74. 24
      main/locales/ResetRequest.json
  75. 10
      main/locales/SearchBox.json
  76. 12
      main/locales/ShortLink.json
  77. 48
      main/locales/Templates.json
  78. 8
      main/locales/TexBlock.json
  79. 14
      main/locales/TextPage.json
  80. 4
      main/locales/TooltipItem.json
  81. 64
      main/locales/UserList.json
  82. 12
      main/locales/Version.json
  83. 8
      main/locales/WaitIcon.json
  84. 84
      main/locales/WarningList.json
  85. 153
      main/server/controller.js
  86. 18
      main/server/router.js
  87. 18
      main/server/server.mjs
  88. 84
      main/server/smtpHelper.js
  89. 196
      main/server/templates/controller.js
  90. 20
      main/server/templates/model.js
  91. 206
      main/server/templates/templates.js
  92. 7
      main/templates/mails/de/register::admin.md
  93. 12
      main/templates/mails/de/register::user.md
  94. 7
      main/templates/mails/de/tan::request.md
  95. 9
      main/templates/mails/de/user::deleted.md
  96. 7
      main/templates/mails/de/user::deleted::admin.md
  97. 9
      main/templates/mails/de/user::expire.md
  98. 4
      main/templates/mails/de/user::limit::hard.md
  99. 4
      main/templates/mails/de/user::limit::soft.md
  100. 10
      main/templates/mails/de/user::pwreset.md
  101. Some files were not shown because too many files have changed in this diff Show More

15
.eslintrc.js

@ -1,15 +0,0 @@
module.exports = {
root: true,
env: {
node: true
},
extends: [
'plugin:vue/vue3-essential',
'@vue/standard'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'brace-style': 'off'
}
}

4
.gitignore vendored

@ -3,6 +3,8 @@
/data
/dist
/app.config.js
/app.config.devel.js
/app.config.production.js
/modules/*
/node_modules
package-lock.json
@ -12,4 +14,4 @@ webpack.dev.js
app.log
/bin/app.log
start.sh
/upload
/upload

3
.stylelintrc.json

@ -1,3 +0,0 @@
{
"extends": "stylelint-config-standard"
}

11
README.md

@ -1,3 +1,7 @@
<a href="https://weblate.bubu1.eu/engage/lerntools/">
<img src="https://weblate.bubu1.eu/widgets/lerntools/-/main/svg-badge.svg" alt="Translation Status" />
</a>
# LernTools - Tools for digital teaching with a focus on privacy
There are many good tools on the internet that support collaborative learning - but unfortunately they often use trackers or involve resources from third parties. This situation was the starting point for developing simple tools that do not process any personal data of students and do not track them or their actions.
@ -42,6 +46,13 @@ See [Wiki](https://codeberg.org/lerntools/base/wiki/Index)
[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.de.html)
### Dependencies
As noted in `Technical basics`, Lerntools use several different external libraries and components. These libraries are released on own licenses which we want partly to explicitly attribute here:
- [vue-material-design-icons](https://www.npmjs.com/package/vue-material-design-icons): MIT license
- [material-icons](https://www.npmjs.com/package/material-icons): Apache-2.0
## Disclaimer of Warranty (see GNU General Public License v3.0)
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

1
app.config.example.js

@ -20,6 +20,7 @@ export default {
urlHome:'', //url of external link from home icon
defaultLocale:'de', //default locale
fixNavbar: true, //always display top navbar at fixed position
editStandardTemplates: false, //for developments, it may be possible to edit standard templates online.
allModules: [
modAbout,
modAbcd,

31
app.config.production.js

@ -1,31 +0,0 @@
/*********************************************************
* Configure app - change according to your setup
*********************************************************/
//modules to import, also add to array allModules below!
import modAbcd from './modules/abcd/config';
import modAbout from './modules/about/config';
import modIdeas from './modules/ideas/config';
import modSurvey from './modules/survey/config';
//import modTex from './modules/tex/config';
import modProjector from './modules/projector/config';
export default {
author:'Lerntools-Team', //author name
pageTitle:'Lerntools', //html head page title
urlBackend:'/', //backend api, leave as '/' when running your own backend
urlImprint:'', //url of external imprint page
urlTerms:'', //url of external terms page
urlPrivacy:'', //url of external privacy page
urlHome:'', //url of external link from home icon
defaultLocale:'de', //default locale
fixNavbar: true, //always display top navbar at fixed position
allModules: [
modAbout,
modAbcd,
modIdeas,
modSurvey,
// modTex,
modProjector
]
}

6
app.js

@ -56,7 +56,11 @@ axios.interceptors.response.use((response) => { return response }, async functio
//failed to refresh token
if (!accessToken) {
store.user.clear();
return router.push({name:'main-login'});
var queryParms = {};
if (router.currentRoute.path !== undefined && router.currentRoute.path !== null) {
queryParms['redirect'] = router.currentRoute.path;
}
return router.push({name:'main-login', query: queryParms});
}
//successfully refreshed token
store.user.accessToken=accessToken;

21
bin/exportDefaultTemplates.js

@ -0,0 +1,21 @@
#! /usr/bin/env node
var Templates = require('../main/server/templates/templates.js');
//Required libraries
var environment = process.env.NODE_ENV;
if (!environment) environment="production";
const serverConfig =require('../server.config.'+environment+'.json');
//Database connection
var mongoose = require('mongoose');
const main = async () => {
mongoose.connect(serverConfig.MONGO_URL);
mongoose.Promise = global.Promise;
var db = mongoose.connection;
mongoose.connection.on('error', console.error.bind(console, 'Error connection to MongoDB:'));
await Templates.exportDefaultTemplates()
db.close();
}
main().then(() => process.exit())

52
bin/extractLocales.py

@ -0,0 +1,52 @@
#!/usr/bin/env python3
import glob
import logging
import os
import sys
import json
LOG = logging.getLogger(__name__)
log_format = logging.Formatter('[%(asctime)s] [%(levelname)s] - %(message)s')
LOG.setLevel(logging.INFO)
# writing to stdout
handler = logging.StreamHandler(sys.stdout)
handler.setLevel(logging.INFO)
handler.setFormatter(log_format)
LOG.addHandler(handler)
def prepareLocalesFolder(localeFile):
folder = os.path.dirname(localeFile)
try:
os.makedirs(folder, exist_ok=True)
except Exception as e:
LOG.error(f'Could not create folder {folder} ({str(e)})')
def convertFile(f):
baseFileName = os.path.basename(f)
localeFileName = os.path.join('locales', baseFileName.replace('.vue', '.json'))
srcLocaleFile = os.path.join('..', localeFileName)
LOG.info(f'Parsing {f} (base: {baseFileName} to locale: {localeFileName}')
# extract the i18n
cnt = open(f, 'rb').read()
try:
i18nStr = cnt.index(b'<i18n>')
except ValueError:
LOG.info(f'File {f} does not contain i18n')
return None
i18nEnd = cnt.index(b'</i18n>')
localeData = cnt[i18nStr:i18nEnd+7]
replaceStr = f'<i18n src="{srcLocaleFile}"/>'.encode('utf-8')
cnt = cnt[:i18nStr] + replaceStr + cnt[i18nEnd+7:]
with open(f, 'wb') as srcFile:
srcFile.write(cnt)
LOG.debug(f'Found locale data in {f}')
prepareLocalesFolder(localeFileName)
localeData = json.loads(localeData[6:-7].decode('utf-8'))
with open(localeFileName, 'wb') as localeFile:
localeFile.write(json.dumps(localeData, indent=4, ensure_ascii=False).encode('utf-8'))
LOG.info(f'Updated {f} and wrote locale file {localeFileName}')
if __name__ == '__main__':
for f in glob.glob('**/*.vue'):
convertFile(f)

16
bin/installDatabase.js

@ -7,7 +7,9 @@ const ADMIN_LOGIN="admin";
const HASH_SALT_ROUNDS= 3;
//Required libraries
const serverConfig =require('../server.config.production.json');
var environment = process.env.NODE_ENV;
if (!environment) environment="production";
const serverConfig =require('../server.config.'+environment+'.json');
const bcrypt = require('bcryptjs');
const generator = require('../main/server/passwordHelper');
@ -20,10 +22,6 @@ mongoose.connection.on('error', console.error.bind(console, 'Error connection to
//Create random password
var password = generator.generate({length: 20, numbers: true});
console.log("**********************************************************");
console.log("Initial Password for user "+ADMIN_LOGIN);
console.log(password);
console.log("**********************************************************");
//Add admin account
var User = require('../main/server/modelUser');
@ -43,7 +41,13 @@ User.findOne({login:ADMIN_LOGIN}, function(err, adminUser) {
active:true });
adminUser.save(function(err) {
if (err) throw err;
else console.log("Initial account and role created successfully");
else {
console.log("Initial account and role created successfully");
console.log("**********************************************************");
console.log("Initial Password for user "+ADMIN_LOGIN);
console.log(password);
console.log("**********************************************************");
}
mongoose.connection.close();
});
}

2
bin/serverDev.sh

@ -1,5 +1,5 @@
#!/bin/bash
export NODE_ENV=development
export DEBUG=abcd,ideas,survey,price,app,chat,share,scripts,main,helpers,memory,bookmark,translate,projector
export DEBUG=abcd,ideas,survey,price,app,chat,share,scripts,main,helpers,memory,bookmark,translate,projector,templates
export PORT=8080
nodemon start

69
main/components/ABCDList.vue

@ -191,71 +191,4 @@ export default {
tr.index-list {}
</style>
<i18n>
{
"de": {
"title":"Titel",
"name":"Name",
"topic": "Thema",
"quiz": "Quiz",
"action": "Aktion",
"start": "Start",
"show":"Anzeigen",
"share": "Einzeltest",
"no-item": "Derzeit ist noch kein Eintrag vorhanden.",
"edit":"Bearbeiten",
"delete":"Löschen",
"export":"Exportieren",
"duplicate":"Duplizieren",
"preview":"Vorschau",
"desc":"Beschreibung",
"subject":"Thema",
"delete-title": "Eintrag löschen",
"delete-info": "Sind Sie sicher, dass Sie den Eintrag „{title}“ löschen möchten?",
"duplicate-title": "Eintrag duplizieren",
"duplicate-info": "Bitte geben Sie einen Titel für die neue Kopie an.",
"import":"Importieren",
"cancel":"Abbrechen",
"import-title": "JSON-Datei importieren",
"import-info": "Wählen Sie die zu importierende json-Datei aus und drücken Sie anschließend „übernehmen“",
"hint-new":"Neu erstellen",
"hint-import":"Importieren",
"owner":"Author",
"ts":"Letzte Änderung",
"created":"Erstellt",
"surveyName":"Vorlage"
},
"en": {
"title": "Titel",
"name":"Name",
"topic": "Topic",
"quiz": "Quiz",
"action": "Action",
"start": "Start",
"show":"Show",
"share": "Self-test",
"no-item": "There is currently no memory game.",
"edit": "Edit",
"delete": "Delete",
"export": "Export",
"duplicate":"Duplicate",
"preview":"Vorschau",
"desc":"Description",
"subject":"Topic",
"delete-title": "delete entry",
"delete-info": "Are you sure you want to delete the entry \"{title}\"?",
"duplicate-title": "Duplicate entry",
"duplicate-info": "Please enter a title for the new copy.",
"import":"Import",
"cancel":"Cancel",
"import-title": "Import JSON file",
"import-info": "Select the json file to be imported and then press \"accept\"",
"hint-new": "Create new",
"hint-import": "Import",
"owner":"Author",
"ts":"Last change",
"created":"Created",
"surveyName":"Template"
}
}
</i18n>
<i18n src="../locales/ABCDList.json"/>

29
main/components/AdminList.vue

@ -109,31 +109,4 @@ export default {
</script>
<i18n>
{
"de": {
"overview": "Übersicht",
"author": "Author",
"title-label": "Titel",
"description": "Beschreibung",
"action": "Aktion",
"delete": "Löschen",
"back": "Zurück",
"timestamp": "Letzte Änderung",
"ticket":"Ticket",
"confirm-delete": "Möchten Sie den Eintrag „{title}“ wirklich löschen?"
},
"en": {
"overview": "Overview",
"author": "Author",
"title-label": "Title",
"description": "Description",
"action": "Action",
"delete": "Delete",
"back": "Back",
"timestamp": "Last change",
"ticket":"ticket",
"confirm-delete": "Do you really want to delete the entry “{title}”?"
}
}
</i18n>
<i18n src="../locales/AdminList.json"/>

11
main/components/EmojiSelect.vue

@ -37,13 +37,4 @@
</script>
<i18n>
{
"de": {
"insert-emoji": "Emoji einfügen"
},
"en": {
"insert-emoji": "Insert Emoji"
}
}
</i18n>
<i18n src="../locales/EmojiSelect.json"/>

15
main/components/FileUpload.vue

@ -105,17 +105,4 @@
}
</style>
<i18n>
{
"de": {
"drop-info":"Ziehen Sie alternativ eine Datei in diesen Bereich um sie auszuwählen",
"selected-file":"Ausgewählte Datei: ",
"reset":"Neu wählen"
},
"en": {
"drop-info":"Either drag your file here or use the upload button above",
"selected-file":"Selected file",
"reset":"Reset"
}
}
</i18n>
<i18n src="../locales/FileUpload.json"/>

69
main/components/IndexList.vue

@ -191,71 +191,4 @@ export default {
tr.index-list {}
</style>
<i18n>
{
"de": {
"title":"Titel",
"name":"Name",
"topic": "Thema",
"quiz": "Quiz",
"action": "Aktion",
"start": "Start",
"show":"Anzeigen",
"share": "Teilen",
"no-item": "Derzeit ist noch kein Eintrag vorhanden.",
"edit":"Bearbeiten",
"delete":"Löschen",
"export":"Exportieren",
"duplicate":"Duplizieren",
"preview":"Vorschau",
"desc":"Beschreibung",
"subject":"Thema",
"delete-title": "Eintrag löschen",
"delete-info": "Sind Sie sicher, dass Sie den Eintrag „{title}“ löschen möchten?",
"duplicate-title": "Eintrag duplizieren",
"duplicate-info": "Bitte geben Sie einen Titel für die neue Kopie an.",
"import":"Importieren",
"cancel":"Abbrechen",
"import-title": "JSON-Datei importieren",
"import-info": "Wählen Sie die zu importierende json-Datei aus und drücken Sie anschließend „übernehmen“",
"hint-new":"Neu erstellen",
"hint-import":"Importieren",
"owner":"Author",
"ts":"Letzte Änderung",
"created":"Erstellt",
"surveyName":"Vorlage"
},
"en": {
"title": "Titel",
"name":"Name",
"topic": "Topic",
"quiz": "Quiz",
"action": "Action",
"start": "Start",
"show":"Show",
"share": "Share",
"no-item": "There is currently no memory game.",
"edit": "Edit",
"delete": "Delete",
"export": "Export",
"duplicate":"Duplicate",
"preview":"Vorschau",
"desc":"Description",
"subject":"Topic",
"delete-title": "delete entry",
"delete-info": "Are you sure you want to delete the entry \"{title}\"?",
"duplicate-title": "Duplicate entry",
"duplicate-info": "Please enter a title for the new copy.",
"import":"Import",
"cancel":"Cancel",
"import-title": "Import JSON file",
"import-info": "Select the json file to be imported and then press \"accept\"",
"hint-new": "Create new",
"hint-import": "Import",
"owner":"Author",
"ts":"Last change",
"created":"Created",
"surveyName":"Template"
}
}
</i18n>
<i18n src="../locales/IndexList.json"/>

120
main/components/MarkdownEditor.vue

@ -0,0 +1,120 @@
<template>
<div class="markdown-editor-wrapper form-group">
<div class="markdown-editor-toolbar">
<ul>
<li @click="handleButton($event, 'bold')"><format-bold-icon /></li>
<li @click="handleButton($event, 'italic')"><format-italic-icon /></li>
<li @click="handleButton($event, 'underline')"><format-underline-icon /></li>
<li @click="handleButton($event, 'strikethrough')"><format-strike-icon /></li>
<li @click="handleButton($event, 'quote')"><format-quote-icon /></li>
<li class="divider">|</li>
<li @click="handleButton($event, 'h1')"><span>H1</span></li>
<li @click="handleButton($event, 'h2')"><span>H2</span></li>
<li @click="handleButton($event, 'h3')"><span>H3</span></li>
<li @click="handleButton($event, 'h4')"><span>H4</span></li>
<li class="divider">|</li>
<li @click="handleButton($event, 'list')"><format-list-icon /></li>
<li @click="handleButton($event, 'list-numbered')"><format-list-numbered-icon /></li>
<li class="divider">|</li>
<li @click="handleButton($event, 'insert-link')"><insert-link-icon /></li>
<li @click="handleButton($event, 'insert-image')"><insert-image-icon /></li>
<li @click="handleButton($event, 'insert-code')"><insert-code-icon /></li>
<li class="divider">|</li>
<li @click="toggleFullscreen($event)"><fullscreen-icon v-if="!isFullscreen" /><fullscreen-exit-icon v-if="isFullscreen" /></li>
</ul>
</div>
<div class="markdown-editor-pane">
<textarea class="form-control" :value="value" rows="15" @input="handleInput($event)"
v-on:change="contentChanged"></textarea>
<div class="markdown-preview" v-html="renderedMd"></div>
</div>
</div>
</template>
<script>
import md from "Main/js/markdown.js";
import MarkdownEditorFunctions from 'Main/js/markdown-editor.js';
import FormatBoldIcon from 'vue-material-design-icons/FormatBold.vue';
import FormatItalicIcon from 'vue-material-design-icons/FormatItalic.vue';
import FormatUnderlineIcon from 'vue-material-design-icons/FormatUnderline.vue';
import FormatStrikeIcon from 'vue-material-design-icons/FormatStrikethroughVariant.vue';
import FormatQuoteIcon from 'vue-material-design-icons/FormatQuoteClose.vue';
import FormatListIcon from 'vue-material-design-icons/FormatListBulleted.vue';
import FormatListNumberedIcon from 'vue-material-design-icons/FormatListNumbered.vue';
import InsertLinkIcon from 'vue-material-design-icons/Link.vue'
import InsertImageIcon from 'vue-material-design-icons/ImagePlus.vue'
import InsertCodeIcon from 'vue-material-design-icons/CodeTags.vue'
import FullscreenIcon from 'vue-material-design-icons/Fullscreen.vue'
import FullscreenExitIcon from 'vue-material-design-icons/FullscreenExit.vue'
export default {
components: {
FormatBoldIcon,FormatItalicIcon,FormatUnderlineIcon,
FormatListIcon, FormatListNumberedIcon, InsertLinkIcon,
InsertImageIcon, MarkdownEditorFunctions, InsertCodeIcon,
FormatStrikeIcon, FormatQuoteIcon,
FullscreenIcon, FullscreenExitIcon
},
name: 'markdown-editor',
props: ['value'],
data() {
return {
content: this.value,
renderedMd: '',
mde: undefined,
isFullscreen: false
}
},
watch: {
value: function() {
this.updateRender();
this.contentChanged();
}
},
methods: {
updateRender() {
this.renderedMd = md.renderFull(this.value);
},
handleInput (e) {
this.content = e.target.value;
this.$emit('input', this.content)
},
contentChanged() {
this.$emit('modified');
},
toolboxActionDone(newValue) {
this.content = newValue;
this.$el.querySelector('textarea').focus();
this.$emit('input', this.content)
},
toggleFullscreen(e) {
var elem = this.$el.classList;
if (this.isFullscreen) {
elem.remove('markdown-editor-fullscreen');
this.isFullscreen = false;
} else {
elem.add('markdown-editor-fullscreen');
this.isFullscreen = true;
}
},
handleButton(e, btnType) {
if (this.mde === undefined) {
this.mde = new MarkdownEditorFunctions.toolbox(
this.$el.querySelector('textarea'),
this.toolboxActionDone
);
}
if (!this.mde) {
return;
}
this.mde.execute(btnType);
}
},
mounted() {
this.updateRender();
}
}
</script>

7
main/components/MenuHuge.vue

@ -49,9 +49,4 @@
</script>
<i18n>
{
"de": {},
"en": {}
}
</i18n>
<i18n src="../locales/MenuHuge.json"/>

11
main/components/MenuLarge.vue

@ -72,13 +72,4 @@
</style>
<i18n>
{
"de": {
"page-restricted": "Diese Seite steht nur berechtigten Benutzern zur Verfügung."
},
"en": {
"page-restricted": "This page is only available to authorized users."
}
}
</i18n>
<i18n src="../locales/MenuLarge.json"/>

11
main/components/MenuSmall.vue

@ -42,13 +42,4 @@
</style>
<i18n>
{
"de": {
"page-restricted": "Diese Seite steht nur berechtigten Benutzern zur Verfügung."
},
"en": {
"page-restricted": "This page is only available to authorized users."
}
}
</i18n>
<i18n src="../locales/MenuSmall.json"/>

7
main/components/MenuSmallEntry.vue

@ -22,9 +22,4 @@
</script>
<i18n>
{
"de": {},
"en": {}
}
</i18n>
<i18n src="../locales/MenuSmallEntry.json"/>

11
main/components/ModalConfirm.vue

@ -16,13 +16,4 @@
</script>
<i18n>
{
"de": {
"confirmation": "Bestätigung"
},
"en": {
"confirmation": "Confirmation"
}
}
</i18n>
<i18n src="../locales/ModalConfirm.json"/>

17
main/components/ModalConfirmCancel.vue

@ -22,19 +22,4 @@
</script>
<i18n>
{
"de": {
"confirmation": "Bestätigung",
"confirm-cancel": "Möchten Sie wirklich abbrechen? Alle ungesicherten Daten gehen dabei verloren.",
"yes": "Ja",
"no": "Nein"
},
"en": {
"confirmation": "Confirmation",
"confirm-cancel": "Are you sure you want to cancel? All unsaved data will be lost.",
"yes": "Yes",
"no": "No"
}
}
</i18n>
<i18n src="../locales/ModalConfirmCancel.json"/>

31
main/components/ModalDialog.vue

@ -12,7 +12,7 @@
<div class="card-footer bg-light">
<slot name="footer">
<button class="btn btn-secondary" v-on:click="$emit('close')">{{$t('cancel')}}</button>
<button class="btn btn-primary mr-1" v-on:click="$emit('accept')">{{$t('accept')}}</button>
<button class="btn btn-primary mr-1" v-on:click="$emit('accept')">{{$t(buttonAcceptTextId)}}</button>
</slot>
</div>
</div>
@ -23,10 +23,23 @@
<script>
export default {
name:"modal-dialog",
props: {
acceptTxtId: String
},
data: function() {
return {
width:0,
defaultAcceptTextId: 'accept'
}
},
computed: {
buttonAcceptTextId: {
set(txt) {
this.defaultAcceptTextId = txt;
},
get() {
return this.acceptTxtId ? this.acceptTxtId : this.defaultAcceptTextId;
}
}
},
created() {
@ -40,6 +53,7 @@
},
mounted: function() {
window.addEventListener('resize', this.setSize);
this.buttonAcceptTextId = 'accept';
this.setSize();
},
destroyed() {
@ -56,15 +70,4 @@
<style scoped src='../css/modal.css'/>
<i18n>
{
"de": {
"cancel": "Abbrechen",
"accept": "Übernehmen"
},
"en": {
"cancel": "Cancel",
"accept": "Accept"
}
}
</i18n>
<i18n src="../locales/ModalDialog.json"/>

13
main/components/ModalInfo.vue

@ -21,15 +21,4 @@
</script>
<i18n>
{
"de": {
"info": "Information",
"close": "Schließen"
},
"en": {
"info": "Information",
"close": "Close"
}
}
</i18n>
<i18n src="../locales/ModalInfo.json"/>

13
main/components/ModalInputTan.vue

@ -27,15 +27,4 @@
</script>
<i18n>
{
"de": {
"confirmation": "Bestätigung erforderlich",
"tan-info": "Zur Bestätigung dieser Aktion ist ein Bestätigungscode notwendig. Bitte prüfen Sie Ihr E-Mail Postfach und geben Sie den Code ein."
},
"en": {
"confirmation": "Confirmation required",
"tan-info": "A confirmation code is required to confirm this action. Please check your e-mail inbox and enter the code."
}
}
</i18n>
<i18n src="../locales/ModalInputTan.json"/>

24
main/components/ModalSettings.vue

@ -17,6 +17,7 @@
<script>
import store from '../js/store.js';
import UserSettings from '../js/user-settings.js';
import ModalInfo from "./ModalInfo.vue";
export default {
components: {ModalInfo},
@ -33,30 +34,11 @@
document.body.setAttribute('data-theme',store.design);
},
lang: function() {
store.lang=this.lang;
this.$root.$i18n.locale=this.lang;
document.documentElement.setAttribute('lang', this.lang);
UserSettings.updateLanguage(this, this.lang);
}
}
}
</script>
<i18n>
{
"de": {
"settings": "Einstellungen",
"design": "Design",
"design-dark": "Dunkles Design",
"design-light": "Helles Design",
"language": "Sprache"
},
"en": {
"settings": "Settings",
"design": "Design",
"design-dark": "Dark design",
"design-light": "Light design",
"language": "Language"
}
}
</i18n>
<i18n src="../locales/ModalSettings.json"/>

19
main/components/ModalShare.vue

@ -64,21 +64,4 @@
<style scoped src='../css/modal.css'/>
<i18n>
{
"de": {
"share": "Teilen",
"shorten": "Kürzen",
"share-info": "Bitte prüfen Sie die Empfänger des Links vor der Weitergabe, da jeder im Besitz dieses Links auch Zugriff auf die Inhalte hat.",
"copy-url": "URL kopieren",
"close": "Schließen"
},
"en": {
"share": "Share",
"shorten": "Shorten",
"share-info": "Please pay attention with whom you share your link, since the resource can be accessed by anyone who has this link.",
"copy-url": "Copy URL",
"close": "Close"
}
}
</i18n>
<i18n src="../locales/ModalShare.json"/>

15
main/components/NavbarBottom.vue

@ -21,17 +21,4 @@ export default {
</style>
<i18n>
{
"de": {
"imprint": "Impressum",
"terms": "Nutzungsbedingungen",
"privacy": "Datenschutz"
},
"en": {
"imprint": "Contact",
"terms": "Terms of usage",
"privacy": "Privacy"
}
}
</i18n>
<i18n src="../locales/NavbarBottom.json"/>

31
main/components/NavbarTop.vue

@ -29,7 +29,7 @@
</div>
</li>
<li class="nav-item" v-else>
<router-link class="nav-link" @click.native="collapsed=true;" :to="{name:'main-login'}">{{$t('menu-login')}}</router-link>
<router-link class="nav-link" @click.native="collapsed=true;" :to="{name:'main-login', query:{'redirect': this.$route.path}}">{{$t('menu-login')}}</router-link>
</li>
</ul>
</div>
@ -104,31 +104,4 @@ export default {
</style>
<i18n>
{
"de": {
"settings": "Einstellungen",
"menu-profile": "Mein Profil",
"menu-administration": "Verwaltung",
"menu-logout": "Abmelden",
"menu-login": "Anmelden",
"menu-info": "Information",
"info-name": "Anwendung",
"info-version": "Version",
"info-author": "Author",
"info-contact": "Kontakt"
},
"en": {
"settings": "Settings",
"menu-profile": "My profile",
"menu-administration": "Management",
"menu-logout": "Logout",
"menu-login": "Login",
"menu-info": "Information",
"info-name": "Application",
"info-version": "Version",
"info-author": "Author",
"info-contact": "Contact"
}
}
</i18n>
<i18n src="../locales/NavbarTop.json"/>

42
main/components/PageTitle-old.vue

@ -1,42 +0,0 @@
<template>
<div class="jumbotron text-center">
<div class="container">
<h1>{{title}}</h1>
<p class="lead">{{subtitle}}</p>
<router-link class="btn btn-secondary" v-if="buttonto" :to="{name: buttonto}">{{button}} </router-link>
<a class="btn btn-secondary" v-else-if="buttonhref && buttonhref.charAt(0)!='#'" target="_blank" :href="buttonhref">{{button}} »</a>
<a class="btn btn-secondary" v-else-if="buttonhref" :href="buttonhref">{{button}} »</a>
<button class="btn btn-secondary" v-else-if="button" v-on:click="$emit('click')">{{button}} </button>
<slot></slot>
</div>
</div>
</template>
<script>
export default {
name: 'page-title',
props: ['title','subtitle','button','buttonto','buttonhref'],
watch: {
title: function() {this.setMeta()},
subtitle: function() {this.setMeta()}
},
mounted: function() {
window.scrollTo(0,0);
this.setMeta();
},
methods: {
setMeta: function() {
document.title=this.title;
document.querySelector('meta[name="description"]').setAttribute("content", this.subtitle ? this.subtitle : '');
}
}
}
</script>
<i18n>
{
"de": {},
"en": {}
}
</i18n>

17
main/components/PageTitle.vue

@ -13,24 +13,24 @@
</div>
</template>
import md from "Main/js/markdown.js";
<script>
import md from "Main/js/markdown.js";
export default {
name: 'page-title',
props: ['title','subtitle','button','buttonto','buttonhref'],
watch: {
title: function() {this.setMeta()},
//subtitle: function() {this.setMeta()}
subtitle: function() {this.escapeText(subtitle)}
subtitle: function() {this.escapeText(this.subtitle)}
},
mounted: function() {
window.scrollTo(0,0);
this.setMeta();
},
methods: {
escapeText: function (text) {
return md.render(text);
},
escapeText: function (text) {
return md.render(text);
},
setMeta: function() {
document.title=this.title;
document.querySelector('meta[name="description"]').setAttribute("content", this.subtitle ? this.subtitle : '');
@ -40,10 +40,5 @@ import md from "Main/js/markdown.js";
</script>
<i18n>
{
"de": {},
"en": {}
}
</i18n>
<i18n src="../locales/PageTitle.json"/>

13
main/components/SearchBox.vue

@ -26,15 +26,4 @@ export default {
</script>
<i18n>
{
"de": {
"hint-search-input": "Geben Sie einen Suchausdruck ein um die Auswahl einzuschränken",
"reset": "Zurücksetzen"
},
"en": {
"hint-search-input": "Enter a search term to narrow down the selection",
"reset": "Reset"
}
}
</i18n>
<i18n src="../locales/SearchBox.json"/>

123
main/components/TemplateEditor.vue

@ -0,0 +1,123 @@
<template>
<modal-dialog v-on:close="$emit('close')" v-on:accept="saveTemplate()" acceptTxtId="save">
<template v-slot:header>{{$t('template-editor')}}</template>
<div class="card-body bg-white">
<warning-list :warnings="warningListTranslated"/>
<slot></slot>
<form>
<div class="form-group row">
<label class="col-form-label">{{$t('type')}}</label>
<select class="form-control" v-model="value.type" v-on:change="checkLoadDefault"
:class="{'is-invalid':!isTypeValid}">
<option v-for="(type, index) in templateTypes" v-bind:key="index" :value="type">{{type}}</option>
</select>
<label class="col-form-label">{{$t('language')}}</label>
<select class="form-control" v-model="value.language" v-on:change="checkLoadDefault"
:class="{'is-invalid':!$v(value.language,true,$consts.REGEX_LANGUAGE)}">
<option v-for="(lng, index) in $consts.SUPPORTED_LANGUAGES" v-bind:key="index" :value="lng">{{lng}}</option>