diff --git a/config/i18next-parser.config.js b/config/i18next-parser.config.js
index 7472d6a5..e92fe5d9 100644
--- a/config/i18next-parser.config.js
+++ b/config/i18next-parser.config.js
@@ -46,7 +46,7 @@ module.exports = {
// mjs: ['JavascriptLexer'],
js: [{
lexer: 'JavascriptLexer',
- functions: ['t', 'addToVerifyList'], // Array of functions to match
+ functions: ['t', 'translate', 'addToVerifyList'], // Array of functions to match
}],
// ts: ['JavascriptLexer'],
// jsx: ['JsxLexer'],
diff --git a/config/i18next.config.js b/config/i18next.config.js
index 3315eecf..aa94e42c 100644
--- a/config/i18next.config.js
+++ b/config/i18next.config.js
@@ -25,6 +25,23 @@ const usedNamespaces = [
'ch01', 'ch02', 'ch03', 'ch04', 'ch05', 'ch06', 'ch07', 'ch08', 'ch09', 'ch10', 'ch11'
]
+// defaultOptions for interpolation
+const i18nDefaultOptions = {
+ cde: '',
+ cde_e: '',
+ em: '',
+ em_e: '',
+ lnk_e: '',
+ str: '',
+ str_e: '',
+
+ // Simple Text, no HTML-Content
+ dqm: '"', // double quotation mark
+ gt: '>',
+ lt: '<',
+ smc: ';' // semicolon
+}
+
const i18nextConfig = {
// debug: true,
@@ -55,3 +72,4 @@ const i18nextConfig = {
exports.i18nextConfig = i18nextConfig
exports.appLanguages = appLanguages
+exports.i18nDefaultOptions = i18nDefaultOptions
diff --git a/lib/challenge-sidebar-handler.js b/lib/challenge-sidebar-handler.js
index 29fd415d..2308c813 100644
--- a/lib/challenge-sidebar-handler.js
+++ b/lib/challenge-sidebar-handler.js
@@ -6,8 +6,8 @@ const path = require('path')
const userData = require(path.normalize(path.join(__dirname, 'user-data.js')))
// Execute after DOM loaded.
-document.addEventListener('DOMContentLoaded', () => {
- const challengeUserData = userData.getChallengeData()
+document.addEventListener('DOMContentLoaded', async () => {
+ const challengeUserData = await userData.getChallengeData()
Object.keys(challengeUserData).forEach((challengeKey) => {
if (challengeUserData[challengeKey].challengeComplete) {
@@ -19,7 +19,7 @@ document.addEventListener('DOMContentLoaded', () => {
/*
* Show a challenge as complete/incomplete in sidebar
*/
-function showChallengeComplete (challengeKey, isComplete) {
+async function showChallengeComplete (challengeKey, isComplete) {
if (isComplete) {
document.getElementById('sidebar__list__item--' + challengeKey).classList.add('complete')
} else {
diff --git a/lib/challenge-verify-handler.js b/lib/challenge-verify-handler.js
index c961040f..bc82b10b 100644
--- a/lib/challenge-verify-handler.js
+++ b/lib/challenge-verify-handler.js
@@ -4,9 +4,9 @@
*/
const path = require('path')
const ipc = require('electron').ipcRenderer
-const i18n = require('electron').remote.getGlobal('i18n')
const userData = require(path.normalize(path.join(__dirname, 'user-data.js')))
const sidebarHandler = require(path.normalize(path.join(__dirname, 'challenge-sidebar-handler.js')))
+const { translate } = require('./renderer-helpers.js')
const currentChallenge = document.head.querySelector('meta[name="currentChallenge"]').getAttribute('content')
const verifyButton = document.getElementById('btn-verify-challenge')
@@ -25,7 +25,7 @@ const selectedDirDiv = document.getElementById('div-selected-dir')
* Execute after DOM loaded.
* Register Button-Handlers
*/
-document.addEventListener('DOMContentLoaded', () => {
+document.addEventListener('DOMContentLoaded', async () => {
verifyButton.addEventListener('click', () => {
handleVerifyClick()
})
@@ -42,7 +42,7 @@ document.addEventListener('DOMContentLoaded', () => {
// Only if button exists.
// Before enableVerifyButtons, due to checked selectedDirPath there.
if (selectDirButton) {
- const savedDir = userData.getSavedDir(currentChallenge)
+ const savedDir = await userData.getSavedDir(currentChallenge)
if (!savedDir) {
showSelectedDirDiv(false)
@@ -52,7 +52,7 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
- const challengeUserData = userData.getChallengeData(currentChallenge)
+ const challengeUserData = await userData.getChallengeData(currentChallenge)
if (challengeUserData.challengeComplete) {
enableVerifyButtons(false)
enableClearStatusButton(true)
@@ -97,7 +97,7 @@ async function handleVerifyClick () {
// In js it is possible to call with more parameters, than defined, not all challenges need the path-parameter
const result = await verifyChallengeScript(selectedDirPath?.innerText)
- clearVerifyList()
+ await clearVerifyList()
printOutVerifyList(result.verifyList)
if (result.challengeComplete) {
@@ -129,13 +129,13 @@ async function clearChallengeStatus () {
/*
* Write out the list to the DOM
*/
-function printOutVerifyList (list) {
+async function printOutVerifyList (list) {
showVerifyList(true)
list.forEach(listItem => {
const li = document.createElement('li')
li.appendChild(document.createTextNode(
- i18n.t(listItem.message, listItem.data)
+ translate(listItem.message, listItem.data)
))
if (listItem.pass) {
li.classList.add('verify__list__elem--pass')
@@ -150,14 +150,14 @@ function printOutVerifyList (list) {
/*
* Small helper for readable code
*/
-function clearVerifyList () {
+async function clearVerifyList () {
verifyList.innerHTML = ''
}
/*
* clearStatusButton gets shown or not on enabled
*/
-function enableClearStatusButton (enabled) {
+async function enableClearStatusButton (enabled) {
if (enabled) {
clearStatusButton.style.display = 'block'
} else {
@@ -169,7 +169,7 @@ function enableClearStatusButton (enabled) {
* VerifyButtons get disabled or not.
* This includes the verify-button, as well as the select-dir button if available.
*/
-function enableVerifyButtons (enabled) {
+async function enableVerifyButtons (enabled) {
verifyButton.disabled = !enabled
if (selectDirButton) {
@@ -185,16 +185,16 @@ function enableVerifyButtons (enabled) {
* Either show selectedDirDiv or show PathRequiredWarning
* If selectedDir is shown, the button should say 'Change Directory', else 'Select Directory'
*/
-function showSelectedDirDiv (show) {
+async function showSelectedDirDiv (show) {
if (show) {
selectedDirDiv.style.display = 'inline'
pathRequiredWarning.style.display = 'none'
- selectDirButton.innerText = i18n.t('verify~Change Directory')
+ selectDirButton.innerText = translate('verify~Change Directory')
selectDirButton.setAttribute('i18n-data', 'verify~Change Directory')
} else {
selectedDirDiv.style.display = 'none'
pathRequiredWarning.style.display = 'inline'
- selectDirButton.innerText = i18n.t('verify~Select directory')
+ selectDirButton.innerText = translate('verify~Select directory')
selectDirButton.setAttribute('i18n-data', 'verify~Select directory')
}
}
@@ -202,7 +202,7 @@ function showSelectedDirDiv (show) {
/*
* Show the verifySpinner or not
*/
-function showSpinner (show) {
+async function showSpinner (show) {
if (show) {
verifySpinner.style.display = 'inline-block'
} else {
@@ -213,7 +213,7 @@ function showSpinner (show) {
/*
* Show verifyList or not
*/
-function showVerifyList (show) {
+async function showVerifyList (show) {
if (show) {
verifyList.style.display = 'block'
} else {
diff --git a/lib/i18n-translate.js b/lib/i18n-translate.js
index 38297c5f..f5a7f484 100644
--- a/lib/i18n-translate.js
+++ b/lib/i18n-translate.js
@@ -7,25 +7,9 @@
* - Listen for Language Dropdown Change
*/
-const i18n = require('electron').remote.getGlobal('i18n')
+const ipc = require('electron').ipcRenderer
const { appLanguages } = require('../config/i18next.config')
-
-// defaultOptions for interpolation
-const defaultOptions = {
- cde: '',
- cde_e: '',
- em: '',
- em_e: '',
- lnk_e: '',
- str: '',
- str_e: '',
-
- // Simple Text, no HTML-Content
- dqm: '"', // double quotation mark
- gt: '>',
- lt: '<',
- smc: ';' // semicolon
-}
+const { translate } = require('./renderer-helpers.js')
/*
* Insert translated Content after loading page.
@@ -41,22 +25,22 @@ document.addEventListener('DOMContentLoaded', () => {
* When changeLanguage is done, the main process will reload the window therefore executing translation-insert from scratch.
*/
const languageSelector = document.getElementById('header__lang__select')
-languageSelector.addEventListener('change', () => {
- i18n.changeLanguage(languageSelector.value)
+languageSelector.addEventListener('change', async () => {
+ ipc.send('i18n.changeLanguage', languageSelector.value)
})
/*
* Select current Language in Dropdown-Element
*/
-function setDropdownLanguage () {
+async function setDropdownLanguage () {
const languageSelector = document.getElementById('header__lang__select')
- languageSelector.value = i18n.language
+ languageSelector.value = ipc.sendSync('i18n.language')
}
/*
* Insert Translations into HTML
*/
-function insertDataTranslations () {
+async function insertDataTranslations () {
const i18nElements = document.querySelectorAll('[i18n-data]')
i18nElements.forEach(element => {
@@ -75,9 +59,9 @@ function insertDataTranslations () {
}
if (type === 'html' || type === 'standard_html') {
- element.innerHTML = i18n.t(data, { ...defaultOptions, ...options })
+ element.innerHTML = translate(data, options)
} else {
- element.innerText = i18n.t(data, { ...defaultOptions, ...options })
+ element.innerText = translate(data, options)
}
})
}
@@ -87,12 +71,12 @@ function insertDataTranslations () {
* Box Titles are done with CSS ::before, so the styles need to be inserted dynamically.
* Here always removing old styles and adding new ones into the specific Stylesheet (see challenges.hbs).
*/
-function insertTranslationStyles () {
+async function insertTranslationStyles () {
const sheetTitle = 'jsTranslationStylesheet'
const sheetIndex = [...document.styleSheets].findIndex(sheet => sheet.title === sheetTitle)
// Insert Style for right-to-left languages
- if (appLanguages[i18n.language].direction === 'rtl') {
+ if (appLanguages[ipc.sendSync('i18n.language')].direction === 'rtl') {
document.getElementById('wrapper__content').classList.add('rtl')
} else {
document.getElementById('wrapper__content').classList.remove('rtl')
@@ -108,9 +92,9 @@ function insertTranslationStyles () {
}
// Append rules for box-titles
if (document.styleSheets[sheetIndex].ownerNode.id === 'challenge-translation-style') {
- document.styleSheets[sheetIndex].insertRule('.box--goal::before {content: "' + i18n.t('Goal') + '";}')
- document.styleSheets[sheetIndex].insertRule('.box--fail::before {content: "' + i18n.t("Didn't Pass?") + '";}')
- document.styleSheets[sheetIndex].insertRule('.box--tip::before {content: "' + i18n.t('Tip') + '";}')
- document.styleSheets[sheetIndex].insertRule('.box--step::before {content: "' + i18n.t('Step') + '";}')
+ document.styleSheets[sheetIndex].insertRule('.box--goal::before {content: "' + translate('Goal') + '";}')
+ document.styleSheets[sheetIndex].insertRule('.box--fail::before {content: "' + translate("Didn't Pass?") + '";}')
+ document.styleSheets[sheetIndex].insertRule('.box--tip::before {content: "' + translate('Tip') + '";}')
+ document.styleSheets[sheetIndex].insertRule('.box--step::before {content: "' + translate('Step') + '";}')
}
}
diff --git a/lib/index-challenge-handler.js b/lib/index-challenge-handler.js
index 88bef8ff..259fedd7 100644
--- a/lib/index-challenge-handler.js
+++ b/lib/index-challenge-handler.js
@@ -18,9 +18,9 @@ const IndexSectionWip = document.getElementById('pgIndex__section--wip')
const IndexSectionFinished = document.getElementById('pgIndex__section--finished')
// Execute after DOM loaded.
-document.addEventListener('DOMContentLoaded', () => {
+document.addEventListener('DOMContentLoaded', async () => {
// Get stored challengeUserData & store Key-Array as often used
- const challengeUserData = userData.getChallengeData()
+ const challengeUserData = await userData.getChallengeData()
const challengeUserDataKeys = Object.keys(challengeUserData)
// Go through the challenges in challengeUserData to see which are complete
@@ -77,7 +77,7 @@ ipc.on('confirm-clearAll', () => {
* Show exclusively the specified section.
* section = 'start'|'wip'|'finished'
*/
-function showSection (section) {
+async function showSection (section) {
// Only accept specific section-keywords
const accept = ['start', 'wip', 'finished']
diff --git a/lib/ipcMainHandlers.js b/lib/ipcMainHandlers.js
index 3732021d..e111656e 100644
--- a/lib/ipcMainHandlers.js
+++ b/lib/ipcMainHandlers.js
@@ -4,21 +4,60 @@
* Done in extra-file to keep main.js neat.
*/
+const fs = require('fs')
const electron = require('electron')
const ipcMain = electron.ipcMain
const dialog = electron.dialog
+// i18n-defaultOptions for Interpolation
+const { i18nDefaultOptions } = require('../config/i18next.config.js')
+
+/**
+ * Register Handlers for IPC
+ */
function registerIpcHandlers (mainWindow, userDataPath) {
- /*
- * Provide userDataPath to Renderer (resp. to lib/user-data.js)
- */
+ /************************
+ * Provide Data to Renderer
+ ************************/
+
+ /**
+ * Provide userDataPath to Renderer (resp. to lib/user-data.js)
+ */
ipcMain.on('getUserDataPath', event => {
event.returnValue = userDataPath
})
- /*
- * Select-Directory Window if called
- */
+ /************************
+ * Provide I18n-Data to Renderer
+ * Some kind of a strange construct, but such there is only one i18n-Object on the main process no remote module is necesssary.
+ ************************/
+
+ /**
+ * Provide translated text
+ */
+ ipcMain.on('i18n.t', (event, data, options) => {
+ event.returnValue = global.i18n.t(data, { ...i18nDefaultOptions, ...options })
+ })
+ /**
+ * Provide current language
+ */
+ ipcMain.on('i18n.language', (event) => {
+ event.returnValue = global.i18n.language
+ })
+ /**
+ * Change current language
+ */
+ ipcMain.on('i18n.changeLanguage', (event, newLang) => {
+ global.i18n.changeLanguage(newLang)
+ })
+
+ /************************
+ * Show User Dialogs
+ ************************/
+
+ /**
+ * Show Directory-select window if called
+ */
ipcMain.on('dialog-selectDir', event => {
const path = dialog.showOpenDialogSync(mainWindow, { properties: ['openDirectory'] })
if (path) {
@@ -26,9 +65,9 @@ function registerIpcHandlers (mainWindow, userDataPath) {
}
})
- /*
- * ClearAll Dialog
- */
+ /**
+ * ClearAll Dialog
+ */
ipcMain.on('dialog-clearAll', event => {
const dialogOptions = {
type: 'info',
@@ -44,6 +83,40 @@ function registerIpcHandlers (mainWindow, userDataPath) {
event.sender.send('confirm-clearAll')
}
})
+
+ /************************
+ * JSON File Operations
+ ************************/
+
+ /**
+ * Async write Data as JSON to file
+ */
+ ipcMain.handle('fs-writeAsJson', async (event, file, data) => {
+ return new Promise((resolve, reject) => {
+ fs.writeFile(file, JSON.stringify(data, null, 2), (err) => {
+ if (err) {
+ reject(err)
+ return
+ }
+ resolve()
+ })
+ })
+ })
+
+ /**
+ * Async read of JSON Data
+ */
+ ipcMain.handle('fs-readFromJson', async (event, file) => {
+ return new Promise((resolve, reject) => {
+ fs.readFile(file, (err, data) => {
+ if (err) {
+ reject(err)
+ return
+ }
+ resolve(JSON.parse(data))
+ })
+ })
+ })
}
exports.registerIpcHandlers = registerIpcHandlers
diff --git a/lib/renderer-helpers.js b/lib/renderer-helpers.js
new file mode 100644
index 00000000..a79f3293
--- /dev/null
+++ b/lib/renderer-helpers.js
@@ -0,0 +1,14 @@
+/*
+ * Runs in: Renderer-Process
+ *
+ * Helpers to be used within renderer-process.
+ */
+
+const ipc = require('electron').ipcRenderer
+
+// Just a small wrapper to ease translation-extraction.
+function translate (data, options = {}) {
+ return ipc.sendSync('i18n.t', data, options)
+}
+
+exports.translate = translate
diff --git a/lib/user-data.js b/lib/user-data.js
index 98cfabd5..64deb5e8 100644
--- a/lib/user-data.js
+++ b/lib/user-data.js
@@ -3,7 +3,6 @@
* This file handles the user-data.json and saved-dir.json files
* Uses electron-remote fs-module to still write out async, even when function gets terminated.
*/
-const fs = require('electron').remote.require('fs')
const ipc = require('electron').ipcRenderer
const path = require('path')
@@ -26,30 +25,30 @@ const verifyDir = {
* user-data.json
*/
// Returns either an object out of all challengesData-objects or if called with parameter the object to the given challenge.
-function getChallengeData (challenge = null) {
- const fileContent = JSON.parse(fs.readFileSync(userDataFile))
+async function getChallengeData (challenge = null) {
+ const fileContent = await readData(userDataFile)
if (challenge) {
return fileContent[challenge]
}
return fileContent
}
// Store challenge verify-data
-function storeChallengeResult (challenge, challengeComplete, verifyList) {
- const fileContent = getChallengeData()
+async function storeChallengeResult (challenge, challengeComplete, verifyList) {
+ const fileContent = await getChallengeData()
fileContent[challenge].challengeComplete = challengeComplete
fileContent[challenge].verifyList = verifyList
writeData(userDataFile, fileContent)
}
// Clear challenge verify-data
-function clearChallengeResult (challenge) {
- const fileContent = getChallengeData()
+async function clearChallengeResult (challenge) {
+ const fileContent = await getChallengeData()
fileContent[challenge].challengeComplete = false
fileContent[challenge].verifyList = []
writeData(userDataFile, fileContent)
}
// Clear all challenges - Set all challenges as incomplete and clear verifyLists
-function clearAllChallenges () {
- const fileContent = getChallengeData()
+async function clearAllChallenges () {
+ const fileContent = await getChallengeData()
Object.keys(fileContent).forEach(challenge => {
fileContent[challenge].challengeComplete = false
fileContent[challenge].verifyList = []
@@ -60,16 +59,16 @@ function clearAllChallenges () {
/****
* saved-dir.json
*/
-function getSavedDir (currentChallenge = null) {
- const fileContent = JSON.parse(fs.readFileSync(savedDirFile))
+async function getSavedDir (currentChallenge = null) {
+ const fileContent = await readData(savedDirFile)
if (currentChallenge) {
return fileContent[verifyDir[currentChallenge]]
}
return fileContent
}
// Store directory
-function updateSavedDir (currentChallenge, path) {
- const fileContent = getSavedDir()
+async function updateSavedDir (currentChallenge, path) {
+ const fileContent = await getSavedDir()
fileContent[verifyDir[currentChallenge]] = path
writeData(savedDirFile, fileContent)
}
@@ -79,12 +78,30 @@ function updateSavedDir (currentChallenge, path) {
* Write out data to file
* Writes out asynchronously.
*/
-function writeData (file, data) {
- fs.writeFile(file, JSON.stringify(data, null, 2), (err) => {
- if (err) {
- console.error(err)
+async function writeData (file, data) {
+ ipc.invoke('fs-writeAsJson', file, data).then(
+ // Resolved
+ () => {
+ // Silent Handle, everything as expected.
+ },
+ // Rejected, sth. went wrong.
+ (rejectErr) => {
+ console.error('Some error occured while writing into', file, rejectErr)
}
- })
+ )
+}
+
+/****
+ * Common function
+ * Read Data from File asynchronously, but wait for returned data.
+ */
+async function readData (file) {
+ try {
+ return await ipc.invoke('fs-readFromJson', file)
+ } catch (err) {
+ console.debug('Some error occured while reading from ', file, err)
+ return {}
+ }
}
exports.getChallengeData = getChallengeData
diff --git a/main.js b/main.js
index 33f3cffb..f08e55bd 100644
--- a/main.js
+++ b/main.js
@@ -33,7 +33,6 @@ app.on('ready', () => {
icon: GititIcon,
webPreferences: {
nodeIntegration: true,
- enableRemoteModule: true,
contextIsolation: false
}
})