diff --git a/package-lock.json b/package-lock.json index 898e00f..f086060 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1426,7 +1426,7 @@ }, "node_modules/mantra-db-models": { "version": "0.0.10", - "resolved": "git+https://code.sigidli.com/mantra/mantra-db-models.git#40423458f5e549406ebf5433073d135497d7ec79", + "resolved": "git+https://code.sigidli.com/mantra/mantra-db-models.git#c26cf2b6a0d789b4441d4c76443ac419ae787b7c", "license": "ISC", "dependencies": { "bcrypt": "^5.0.1", @@ -1518,9 +1518,9 @@ } }, "node_modules/minipass": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", - "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.3.tgz", + "integrity": "sha512-N0BOsdFAlNRfmwMhjAsLVWOk7Ljmeb39iqFlsV1At+jqRhSUP9yeof8FyJu4imaJiSUp8vQebWD/guZwGQC8iA==", "dependencies": { "yallist": "^4.0.0" }, @@ -4011,7 +4011,7 @@ } }, "mantra-db-models": { - "version": "git+https://code.sigidli.com/mantra/mantra-db-models.git#40423458f5e549406ebf5433073d135497d7ec79", + "version": "git+https://code.sigidli.com/mantra/mantra-db-models.git#c26cf2b6a0d789b4441d4c76443ac419ae787b7c", "from": "mantra-db-models@git+https://code.sigidli.com/mantra/mantra-db-models.git", "requires": { "bcrypt": "^5.0.1", @@ -4079,9 +4079,9 @@ } }, "minipass": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", - "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.3.tgz", + "integrity": "sha512-N0BOsdFAlNRfmwMhjAsLVWOk7Ljmeb39iqFlsV1At+jqRhSUP9yeof8FyJu4imaJiSUp8vQebWD/guZwGQC8iA==", "requires": { "yallist": "^4.0.0" } diff --git a/server/router/projects/index.js b/server/router/projects/index.js index 55dbb16..c90b00e 100644 --- a/server/router/projects/index.js +++ b/server/router/projects/index.js @@ -1164,6 +1164,21 @@ module.exports = function (options) { }, { association: db.TranslationChapter.TranslationChapterTranslators, + include: [ + { + association: db.TranslationChapterTranslator.Translator, + include: [ + { + association: db.Entity.EntityEmail, + include: [ + { + association: db.EntityEmail.Email + } + ] + } + ] + } + ] }, { association: db.TranslationChapter.TranslationChapterProofReaders, diff --git a/server/router/translate/index.js b/server/router/translate/index.js index a6e155c..6579a1a 100644 --- a/server/router/translate/index.js +++ b/server/router/translate/index.js @@ -1,5 +1,6 @@ const express = require('express') const { Op } = require("sequelize") +const idGenerator = require('mantra-db-models/src/lib/id-generator') module.exports = function (options) { const db = options.db; @@ -111,6 +112,21 @@ module.exports = function (options) { }, { association: db.TranslationChapter.TranslationChapterTranslators, + include: [ + { + association: db.TranslationChapterTranslator.Translator, + include: [ + { + association: db.Entity.EntityEmail, + include: [ + { + association: db.EntityEmail.Email + } + ] + } + ] + } + ] }, { association: db.TranslationChapter.TranslationChapterProofReaders, @@ -849,5 +865,208 @@ module.exports = function (options) { next(error) }) }) + + router.route('/:id/chapter/:chapterId/translator') + .get(function(request, response, next) { + db.TranslationChapter.findByPk(request.params.chapterId, { + include: [ + { + association: db.TranslationChapter.Chapter, + }, + { + association: db.TranslationChapter.TranslationChapterTranslators, + }, + { + association: db.TranslationChapter.TranslationChapterProofReaders, + }, + { + association: db.TranslationChapter.TranslationChunks, + include: [ + { + association: db.TranslationChunk.Translation + } + ] + }, + { + association: db.TranslationChapter.TranslationArtifactVersion, + include: [ + { + association: db.TranslationArtifactVersion.ArtifactVersion, + include: [ + { + association: db.ArtifactVersion.Artifact, + include: [ + { + association: db.Artifact.Dialect + } + ] + } + ] + }, + { + association: db.TranslationArtifactVersion.Dialect + }, + { + association: db.TranslationArtifactVersion.BackTranslationFrom + }, + { + association: db.TranslationArtifactVersion.TranslationArtifactVersionEditor, + } + ] + } + ] + }).then(translationChapter => { + if (translationChapter) { + response.display("translate-chapter-translator-form", { + user: request.user, + pageTitle: `Translate Chapter ${translationChapter.name}`, + translationChapter: translationChapter + }) + } else { + next() + } + }).catch(error => { + next(error) + }) + }) + .post(function(request, response, next) { + db.TranslationChapter.findByPk(request.params.chapterId, { + include: [ + { + association: db.TranslationChapter.Chapter, + }, + { + association: db.TranslationChapter.TranslationChapterTranslators, + }, + { + association: db.TranslationChapter.TranslationChapterProofReaders, + }, + { + association: db.TranslationChapter.TranslationChunks, + include: [ + { + association: db.TranslationChunk.Translation + } + ] + }, + { + association: db.TranslationChapter.TranslationArtifactVersion, + include: [ + { + association: db.TranslationArtifactVersion.ArtifactVersion, + include: [ + { + association: db.ArtifactVersion.Artifact, + include: [ + { + association: db.Artifact.Dialect + } + ] + } + ] + }, + { + association: db.TranslationArtifactVersion.Dialect + }, + { + association: db.TranslationArtifactVersion.BackTranslationFrom + }, + { + association: db.TranslationArtifactVersion.TranslationArtifactVersionEditor, + } + ] + } + ] + }).then(async (translationChapter) => { + if (translationChapter) { + // TODO: Check if translationChapter has a TranslationChapterTranslator with this email... + // TODO: FindOrCreate Email + const [email, created] = await db.Email.findOrCreate({ + where: { + address: request.body.translator + }, + include: [ + { + association: db.Email.EntityEmails, + required: false, + where: { + creatorId: request.user.id + } + }, + { + association: db.Email.UserEmail, + include: [ + { + association: db.UserEmail.User, + include: [ + { + association: db.User.IndividualEntityUser, + include: [ + { + association: db.IndividualEntityUser.EntityUser + } + ] + } + ] + } + ] + } + ], + defaults: { + address: request.body.translator + } + }) + console.log("Email: ", email.toJSON()) + if (email.userEmail) { + // if email has user associated with it tie that users individual entity to a new TranslationChapterTranslator object + await db.TranslationChapterTranslator.create({ + translationChapterId: translationChapter.id, + creatorId: request.user.id, + translatorId: email.userEmail.user.individualEntityUser.entityUser.entityId + }) + } else if (email.entityEmails?.length > 0) { + // TODO: If email has an EntityEmail (which has this user as a creator) tie that entityEmail to a new TranslationChapterTranslator + await db.TranslationChapterTranslator.create({ + translationChapterId: translationChapter.id, + creatorId: request.user.id, + translatorId: email.entityEmails[0].entityId + }) + } else { + // if email doesn't have a user/entityEmail associated with it (newly created) create an EntityEmail and tie it to a new TranslationChapterTranslator object + await db.TranslationChapterTranslator.create({ + translationChapterId: translationChapter.id, + creatorId: request.user.id, + translator: { + name: idGenerator.generateRandomAlphanumeric(), + type: "individual", + entityEmail: { + creatorId: request.user.id, + emailAddress: email.address, + type: 'translator' + } + } + }, { + include: [ + { + association: db.TranslationChapterTranslator.Translator, + include: [ + { + association: db.Entity.EntityEmail + } + ] + } + ] + }) + } + + response.redirect(`/translate/${translationChapter.translationArtifactVersion.id}/chapter/${translationChapter.id}`) + } else { + next() + } + }).catch(error => { + next(error) + }) + }) + return router; }; \ No newline at end of file diff --git a/server/views/css/site.css b/server/views/css/site.css index c667a3a..5ee70ae 100644 --- a/server/views/css/site.css +++ b/server/views/css/site.css @@ -59,4 +59,18 @@ html { .tracker-dropdown { margin-left: 0; margin-top: -2px; +} + +.initial-icon { + display:inline-block; + font-size:1em; + width:2.5em; + height:2.5em; + line-height:2.5em; + text-align:center; + border-radius:50%; + background:plum; + vertical-align:middle; + margin-right:1em; + color:white; } \ No newline at end of file diff --git a/server/views/js/init-translator-autocomplete.js b/server/views/js/init-translator-autocomplete.js new file mode 100644 index 0000000..78a1539 --- /dev/null +++ b/server/views/js/init-translator-autocomplete.js @@ -0,0 +1,4 @@ +document.addEventListener('DOMContentLoaded', function() { + var elems = document.querySelectorAll('#translator-input'); + var instances = M.Autocomplete.init(elems, {}); +}); \ No newline at end of file diff --git a/server/views/js/init-translator-dropdown.js b/server/views/js/init-translator-dropdown.js new file mode 100644 index 0000000..851d549 --- /dev/null +++ b/server/views/js/init-translator-dropdown.js @@ -0,0 +1,4 @@ +document.addEventListener('DOMContentLoaded', function() { + var elems = document.querySelectorAll('#translator-dropdown'); + var instances = M.Dropdown.init(elems, {}); +}); \ No newline at end of file diff --git a/server/views/project-tracker.pug b/server/views/project-tracker.pug index a22e9e2..9349835 100644 --- a/server/views/project-tracker.pug +++ b/server/views/project-tracker.pug @@ -45,18 +45,43 @@ block content a.dropdown-trigger editor ▼ tbody each translationChapter in translationChapters.sort((a, b) => a.index - b.index) - tr(onclick=`window.location='/translate/${translationChapter.translationArtifactVersion.id}/chapter/${translationChapter.id}'`) + - + const onclickResponse = `window.location='/translate/${translationChapter.translationArtifactVersion.id}/chapter/${translationChapter.id}'` + tr //- TODO: link to the translationChapter... td p label - input(type="checkbox") + //- TODO: Make this a issueId... + input(name="selectedTranslationChapters", type="checkbox", value=translationChapter.id) span - td.truncate(title=translationChapter.chapter.name)= translationChapter.chapter.name - td + td(onclick=onclickResponse) + span.truncate(title=translationChapter.chapter.name)= translationChapter.chapter.name + td(onclick=onclickResponse) a(href=`/projects/${project.id}/tracker?dialectId=${translationChapter.translationArtifactVersion.dialect.id}`)= translationChapter.translationArtifactVersion.dialect.name - td #{translationChapter.translationArtifactVersion.artifactVersion.artifact.name} - #{translationChapter.translationArtifactVersion.artifactVersion.tag} - td= translationChapter.index - td - td - td \ No newline at end of file + - + const artifactName = `${translationChapter.translationArtifactVersion.artifactVersion.artifact.name} - ${translationChapter.translationArtifactVersion.artifactVersion.tag}` + td(onclick=onclickResponse) + span.truncate(title=artifactName)= artifactName + td(onclick=onclickResponse)= translationChapter.index + td(onclick=onclickResponse) + each translationChapterTranslator in translationChapter.translationChapterTranslators + a(href=`/projects/${project.id}/tracker?translatorId=${translationChapterTranslator.translatorId}`) + - + const translatorDisplayName = translationChapterTranslator.translator.entityEmail == null ? translationChapterTranslator.translator.name : translationChapterTranslator.translator.entityEmail.email.address + const stringToColour = function(str) { + var hash = 0; + for (var i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + var colour = '#'; + for (var i = 0; i < 3; i++) { + var value = (hash >> (i * 8)) & 0xFF; + colour += ('00' + value.toString(16)).substr(-2); + } + return colour; + } + + span.initial-icon(style=`background: ${stringToColour(translationChapterTranslator.translatorId)};`, title=translatorDisplayName)= translatorDisplayName.split(" ").map(l => l.at(0)).join("") + td(onclick=onclickResponse) + td(onclick=onclickResponse) \ No newline at end of file diff --git a/server/views/translate-chapter-translator-form.pug b/server/views/translate-chapter-translator-form.pug new file mode 100644 index 0000000..6ff5fc2 --- /dev/null +++ b/server/views/translate-chapter-translator-form.pug @@ -0,0 +1,28 @@ +extend templates/layout.pug + +block content + .container + .center + h1 #{translationChapter.translationArtifactVersion.artifactVersion.artifact.dialect.name} to #{translationChapter.translationArtifactVersion.name} + h2= translationChapter.translationArtifactVersion.artifactVersion.artifact.name + + p.flow-text + span.chip #{translationChapter.chapter.wordCount.toLocaleString()} words + + p.flow-text Add translator + + + form.row(action=`/translate/${translationChapter.translationArtifactVersion.id}/chapter/${translationChapter.id}/translator`, method="post") + .col.s12 + .row + .input-field.col.s12 + i.material-icons.prefix translate + input#translator-input.autocomplete(type="text", name="translator", required=true) + + label(for="translator-input") Enter translators email + //- TODO add other functionality... phone number or username + + +block additionalScripts + script + include js/init-translator-autocomplete.js \ No newline at end of file diff --git a/server/views/translate-chapter.pug b/server/views/translate-chapter.pug index 6982e58..e30c1a5 100644 --- a/server/views/translate-chapter.pug +++ b/server/views/translate-chapter.pug @@ -13,9 +13,13 @@ block content .col.s12.m4 p.flow-text - a.btn-flat.waves-effect Translator ▼ + a.btn-flat.waves-effect.dropdown-trigger(href=`/translate/${translationChapter.translationArtifactVersion.id}/chapter/${translationChapter.id}/translator`) Translator ▼ + br if translationChapter.translationChapterTranslators.length > 0 - span With #{translationChapter.translationChapterTranslators.length} translator(s) assigned + each translationChapterTranslator in translationChapter.translationChapterTranslators + - + const translatorDisplayName = translationChapterTranslator.translator.entityEmail == null ? translationChapterTranslator.translator.name : translationChapterTranslator.translator.entityEmail.email.address + span.chip= translatorDisplayName else span No translators assigned yet .col.s12.m4 @@ -64,4 +68,8 @@ block content else if translationChunk.translation a.btn.blue(href=`/translate/${translationChapter.translationArtifactVersion.id}/chapter/${translationChapter.id}/t/${translationChunk.index}`) edit else - a.btn.black(href=`/translate/${translationChapter.translationArtifactVersion.id}/chapter/${translationChapter.id}/t/${translationChunk.index}`) translate \ No newline at end of file + a.btn.black(href=`/translate/${translationChapter.translationArtifactVersion.id}/chapter/${translationChapter.id}/t/${translationChunk.index}`) translate + +block additionalScripts + script + include js/init-translator-dropdown.js \ No newline at end of file