From c8c084868b87d4838692a7db6667f9e0ed53cc86 Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 14 Jun 2020 22:33:58 +0900 Subject: [PATCH 01/14] Update ruby version to 2.6. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7ed2ac45..18872f27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM ruby:2.5 +FROM ruby:2.6 LABEL maintainer="AKIKO TAKANO / (Twitter: @akiko_pusu)" \ description="Image to run Redmine simply with sqlite to try/review plugin." From c46878cb36afef7c0857def5f2be51174f22e00f Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 14 Jun 2020 22:43:31 +0900 Subject: [PATCH 02/14] Change table css class. (Specify the class for issue template.) --- app/views/common/_orphaned.html.erb | 2 +- app/views/global_issue_templates/index.html.erb | 2 +- app/views/issue_templates/index.html.erb | 6 +++--- app/views/note_templates/index.html.erb | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/views/common/_orphaned.html.erb b/app/views/common/_orphaned.html.erb index f107519b..8ec86702 100644 --- a/app/views/common/_orphaned.html.erb +++ b/app/views/common/_orphaned.html.erb @@ -1,5 +1,5 @@

<%= l(:orphaned_template) %>

- +
diff --git a/app/views/global_issue_templates/index.html.erb b/app/views/global_issue_templates/index.html.erb index e46eec88..fbcdf159 100644 --- a/app/views/global_issue_templates/index.html.erb +++ b/app/views/global_issue_templates/index.html.erb @@ -21,7 +21,7 @@

<%= tracker.name %>

-
#
+
diff --git a/app/views/issue_templates/index.html.erb b/app/views/issue_templates/index.html.erb index 55c4f8a3..f9b697c3 100644 --- a/app/views/issue_templates/index.html.erb +++ b/app/views/issue_templates/index.html.erb @@ -23,7 +23,7 @@

<%= tracker.name %>

<%= non_project_tracker_msg(project_tracker?(tracker, @project)) %> -
#
+
@@ -99,7 +99,7 @@

<%=h "#{l(:label_inherited_templates)}" %>

-
#
+
@@ -180,7 +180,7 @@ <% end %> <%= l(:only_admin_can_associate_global_template) %> -
#
+
diff --git a/app/views/note_templates/index.html.erb b/app/views/note_templates/index.html.erb index baca606c..6a474cb0 100644 --- a/app/views/note_templates/index.html.erb +++ b/app/views/note_templates/index.html.erb @@ -22,7 +22,7 @@

<%= tracker.name %>

-
#
+
From 377b3eafbb26a07b27b11ce7d52fa209c8af1638 Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Wed, 17 Jun 2020 20:36:17 +0900 Subject: [PATCH 03/14] Commit for PR. From e83474cf2226b09bb44900ef2602a624ef895a5f Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Wed, 17 Jun 2020 20:42:36 +0900 Subject: [PATCH 04/14] Add workaround. --- .../_issue_select_form.html.erb | 60 ++++++++++++++----- assets/javascripts/issue_templates.js | 14 ++--- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/app/views/issue_templates/_issue_select_form.html.erb b/app/views/issue_templates/_issue_select_form.html.erb index f9f8f806..6cf00454 100644 --- a/app/views/issue_templates/_issue_select_form.html.erb +++ b/app/views/issue_templates/_issue_select_form.html.erb @@ -72,29 +72,60 @@ diff --git a/assets/javascripts/issue_templates.js b/assets/javascripts/issue_templates.js index a74b4f1e..e84138d8 100644 --- a/assets/javascripts/issue_templates.js +++ b/assets/javascripts/issue_templates.js @@ -63,7 +63,7 @@ ISSUE_TEMPLATE.prototype = { let issueDescription = document.getElementById('issue_description') let oldDescription = document.getElementById('original_description') - let templateNS = this + let templateNS = window.templateNS issueSubject.value = templateNS.escapeHTML(oldSubject.textContent) @@ -82,13 +82,13 @@ ISSUE_TEMPLATE.prototype = { oldDescription.textContent = '' document.getElementById('revert_template').classList.add('disabled') }, - load_template: (confirm_flg) => { + load_template: function (confirm_flg) { let confirmFlg = true if (confirm_flg != null) { confirmFlg = confirm_flg } - let ns = templateNS + var ns = this let selectedTemplate = document.getElementById('issue_template') if (selectedTemplate.value === '') return @@ -202,7 +202,7 @@ ISSUE_TEMPLATE.prototype = { }) }, confirmToReplaceMsg: () => { - let ns = templateNS + var ns = this let dialog = document.getElementById('issue_template_confirm_to_replace_dialog') dialog.style.visibility = 'visible' dialog.classList.add('active') @@ -248,7 +248,7 @@ ISSUE_TEMPLATE.prototype = { templateStatusArea.appendChild(messageElement) }, - getCsrfToken: () => { + getCsrfToken: function () { const metas = document.getElementsByTagName('meta') for (let meta of metas) { if (meta.getAttribute('name') === 'csrf-token') { @@ -258,7 +258,7 @@ ISSUE_TEMPLATE.prototype = { return '' }, set_pulldown: function (tracker) { - let ns = this + var ns = this fetch(ns.pulldownUrl, { method: 'POST', @@ -345,7 +345,7 @@ ISSUE_TEMPLATE.prototype = { }, // support built-in field update builtin_fields: (template) => { - let ns = templateNS + var ns = this let builtinFieldsJson = template.builtin_fields_json if (builtinFieldsJson === undefined) return false From 08828b0081a38f4530eed79833086e1d23da33fe Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sat, 20 Jun 2020 11:14:14 +0900 Subject: [PATCH 05/14] Update README about 0.3-stable branch. --- README.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 6953565b..64f248b2 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Plugin to generate and use issue templates for each project to assist issue creation. The latest version 1.0.x **is not compatible with IE11**. (Related: #310) -Please use version 0.3.7 or **0.3-stable branch** (uing jQuery version) as a stable release for Redmine4.x. +Please use version 0.3.7 or **[0.3-stable](https://github.com/akiko-pusu/redmine_issue_templates/tree/0.3-stable) branch** (uing jQuery version) as a stable release for Redmine4.x. ## Repository @@ -143,7 +143,7 @@ Thank you for the valuable information and feedback, @AlUser71! ### 1.0.0 RESTRICTION: This version **is not compatible with IE11**. (Related: #310) -Please use version **0.3.7** or **0.3-stable branch** (uing jQuery version) if you need to support IE11. +Please use version **0.3.7** or **[0.3-stable](https://github.com/akiko-pusu/redmine_issue_templates/tree/0.3-stable) branch** (uing jQuery version) if you need to support IE11. NOTE: **Migration is required**. Since ``Support Built-In / Custom Fields`` is an experimental feature, please **be careful** if you hope to try it. @@ -156,6 +156,17 @@ Since ``Support Built-In / Custom Fields`` is an experimental feature, please ** And some browsers may not work fine because Support Built-In / Custom Fields feature uses Vue.js for frontend. So feedback, issue report, suggestion highly appreciate! +### 0.3.8 + +This is bugfix release. + +* Bugfix: Fix that Issue Templates plugin changes the cursor icon for "Information" menu on Redmine's administration page (by vividtone, GitHub #316) +* Bugfix: Orphaned template list is not displayed (GitHub #337) +* Update Russian translation (GitHub #340) +* Update Bulgarian translation (GitHub #329) +* Update Korean translation (update Korean translation) +* Bugfix: enabled to create a new issue template setting. (GitHub #322) + ### 0.3.7 This is bugfix release to prevent the conflict with other plugins. @@ -165,7 +176,6 @@ This is bugfix release to prevent the conflict with other plugins. Thank you for the valuable information and feedback, @ChrisUHZ! - ### 0.3.6 This is bugfix release against v0.3.5. From 05f6ddee7122db5372834f52443db7c9da5f7dd2 Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 21 Jun 2020 11:51:59 +0900 Subject: [PATCH 06/14] Enabled to change target project if project_id is passed by queryString or post parameter. --- app/controllers/issue_templates_controller.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/controllers/issue_templates_controller.rb b/app/controllers/issue_templates_controller.rb index 1aa1bb88..f1e58328 100644 --- a/app/controllers/issue_templates_controller.rb +++ b/app/controllers/issue_templates_controller.rb @@ -203,6 +203,9 @@ def add_templates_to_group(templates, option = {}) end def issue_templates + if params[:issue_project_id] + @project = Project.find(params[:issue_project_id]) + end IssueTemplate.get_templates_for_project_tracker(@project.id, @tracker.id) end From aba0ac1934a4632b1f1c6b7780b529dc8f1410a2 Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 21 Jun 2020 12:21:41 +0900 Subject: [PATCH 07/14] Refactor JavaScript and feature spec to support Redmine trunk. --- .../_issue_select_form.html.erb | 198 +++++------ app/views/issue_templates/_note_form.html.erb | 102 +----- .../_template_pulldown.html.erb | 1 - .../_list_note_templates.html.erb | 3 +- assets/javascripts/issue_templates.js | 329 ++++++++++++------ assets/stylesheets/issue_templates.css | 9 +- spec/features/issue_template_spec.rb | 4 + spec/features/update_issue_spec.rb | 4 +- 8 files changed, 331 insertions(+), 319 deletions(-) diff --git a/app/views/issue_templates/_issue_select_form.html.erb b/app/views/issue_templates/_issue_select_form.html.erb index 6cf00454..9f389a9b 100644 --- a/app/views/issue_templates/_issue_select_form.html.erb +++ b/app/views/issue_templates/_issue_select_form.html.erb @@ -39,11 +39,12 @@

×

- - +
+ + +
@@ -72,123 +73,82 @@ diff --git a/app/views/issue_templates/_note_form.html.erb b/app/views/issue_templates/_note_form.html.erb index 42015e49..52173a9c 100644 --- a/app/views/issue_templates/_note_form.html.erb +++ b/app/views/issue_templates/_note_form.html.erb @@ -10,7 +10,7 @@ - + <%=h l(:display_and_filter_issue_templates_in_dialog, default: 'Filter Templates') %> @@ -30,96 +30,24 @@ diff --git a/app/views/issue_templates/_template_pulldown.html.erb b/app/views/issue_templates/_template_pulldown.html.erb index 46d78525..8d68723d 100644 --- a/app/views/issue_templates/_template_pulldown.html.erb +++ b/app/views/issue_templates/_template_pulldown.html.erb @@ -2,4 +2,3 @@ <%= options_for_template_pulldown(grouped_options) %> - diff --git a/app/views/note_templates/_list_note_templates.html.erb b/app/views/note_templates/_list_note_templates.html.erb index 4b0c4f5c..78da7075 100644 --- a/app/views/note_templates/_list_note_templates.html.erb +++ b/app/views/note_templates/_list_note_templates.html.erb @@ -49,7 +49,8 @@
diff --git a/assets/javascripts/issue_templates.js b/assets/javascripts/issue_templates.js index e84138d8..f9846760 100644 --- a/assets/javascripts/issue_templates.js +++ b/assets/javascripts/issue_templates.js @@ -1,27 +1,26 @@ /* * To change this template, choose Tools | Templates * and open the template in the editor. + * + * Use '==' operator to evaluate null or undefined. */ // For namespace setting. // var ISSUE_TEMPLATE = ISSUE_TEMPLATE || function () {} -function ISSUE_TEMPLATE(pulldownUrl, loadUrl, confirmMsg, shouldReplaced, confirmToReplace, - confirmation, generalTextYes, generalTextNo, isTriggeredBy) { - this.pulldownUrl = pulldownUrl - this.loadUrl = loadUrl - this.confirmMsg = confirmMsg - this.shouldReplaced = shouldReplaced - this.confirmToReplace = confirmToReplace - this.confirmation = confirmation - this.generalTextYes = generalTextYes - this.generalTextNo = generalTextNo - this.isTriggeredBy = isTriggeredBy +function ISSUE_TEMPLATE(config) { + this.pulldownUrl = config.pulldownUrl + this.loadUrl = config.loadUrl + this.confirmMsg = config.confirmMessage + this.shouldReplaced = config.shouldReplaced + this.generalTextYes = config.generalTextYes + this.generalTextNo = config.generalTextNo + this.isTriggeredBy = config.isTriggeredBy } ISSUE_TEMPLATE.prototype = { clearValue: (id) => { let target = document.getElementById(id) - if (target === null) { + if (target == null) { return } target.value = '' @@ -67,7 +66,7 @@ ISSUE_TEMPLATE.prototype = { issueSubject.value = templateNS.escapeHTML(oldSubject.textContent) - if (issueDescription !== null) { + if (issueDescription != null) { issueDescription.value = templateNS.escapeHTML(oldDescription.textContent) } @@ -82,14 +81,9 @@ ISSUE_TEMPLATE.prototype = { oldDescription.textContent = '' document.getElementById('revert_template').classList.add('disabled') }, - load_template: function (confirm_flg) { - let confirmFlg = true - if (confirm_flg != null) { - confirmFlg = confirm_flg - } - - var ns = this + loadTemplate: function () { let selectedTemplate = document.getElementById('issue_template') + let ns = this if (selectedTemplate.value === '') return @@ -120,89 +114,91 @@ ISSUE_TEMPLATE.prototype = { // when operator submits new issue form without required field and returns // with error message. If flash message #errorExplanation exists, not overwrited. // (https://github.com/akiko-pusu/redmine_issue_templates/issues/50) - if (document.querySelector('#errorExplanation') && document.querySelector('#errorExplanation')[0]) return + if (document.querySelector('#errorExplanation') && document.querySelector('#errorExplanation')[0]) { + document.querySelector('#errorExplanation') + return + } // Returned JSON may have the key named 'global_template' or 'issue_template' let parsedData = JSON.parse(data) let templateKey = Object.keys(parsedData)[0] let obj = parsedData[templateKey] - obj.description = (obj.description === null) ? '' : obj.description - obj.issue_title = (obj.issue_title === null) ? '' : obj.issue_title + obj.description = (obj.description == null) ? '' : obj.description + obj.issue_title = (obj.issue_title == null) ? '' : obj.issue_title - let oldSubj = '' - let oldVal = '' let issueSubject = document.getElementById('issue_subject') let issueDescription = document.getElementById('issue_description') - if (confirmFlg === true && ns.confirmToReplace === true && ns.shouldReplaced === 'true' && (issueSubject.value !== '')) { - if (oldSubj !== obj.issue_title) { + this.loadedTemplate = obj + + if (ns.shouldReplaced === 'true' && (issueDescription.value !== '' || issueSubject.value !== '')) { + if (obj.description !== '' || obj.issue_title !== '') { let hideConfirmFlag = ns.hideOverwiteConfirm() if (hideConfirmFlag === false) { - return ns.confirmToReplaceMsg() + return ns.confirmToReplaceContent(obj) } } } + ns.replaceTemplateValue(obj) + }) + }, + replaceTemplateValue: function (obj) { + let ns = this - // for description - if (issueDescription !== null) { - let originalDescription = document.getElementById('original_description') - if (issueDescription.value !== '' && ns.shouldReplaced === 'false') { - oldVal = issueDescription.value + '\n\n' - } - - originalDescription.textContent = issueDescription.value + let oldSubj = '' + let oldVal = '' + let issueSubject = document.getElementById('issue_subject') + let issueDescription = document.getElementById('issue_description') - issueDescription.getAttribute('original_description', issueDescription.value) - if (oldVal.replace(/(?:\r\n|\r|\n)/g, '').trim() !== obj.description.replace(/(?:\r\n|\r|\n)/g, '').trim()) { - issueDescription.value = oldVal + obj.description - } - } + if (issueDescription != null) { + let originalDescription = document.getElementById('original_description') + if (issueDescription.value !== '' && ns.shouldReplaced === 'false') { + oldVal = issueDescription.value + '\n\n' + } - let originalSubject = document.getElementById('original_subject') - if (issueSubject.value !== '' && ns.shouldReplaced === 'false') { - oldSubj = issueSubject.value + ' ' - } - originalSubject.textContent = issueSubject.value + originalDescription.textContent = issueDescription.value - issueSubject.setAttribute('original_title', issueSubject.value) - if (oldSubj.trim() !== obj.issue_title.trim()) { - issueSubject.value = oldSubj + obj.issue_title - } + issueDescription.getAttribute('original_description', issueDescription.value) + if (oldVal.replace(/(?:\r\n|\r|\n)/g, '').trim() !== obj.description.replace(/(?:\r\n|\r|\n)/g, '').trim()) { + issueDescription.value = oldVal + obj.description + } + } - try { - if (CKEDITOR.instances.issue_description) { - CKEDITOR.instances.issue_description.setData(oldVal + obj.description) - } - } catch (e) { - // do nothing. - } - // show message just after default template loaded. - if (ns.confirmMsg) { - ns.show_loaded_message(ns.confirmMsg, issueSubject) - } + let originalSubject = document.getElementById('original_subject') + if (issueSubject.value !== '' && ns.shouldReplaced === 'false') { + oldSubj = issueSubject.value + ' ' + } + originalSubject.textContent = issueSubject.value - if (originalSubject.textContent.length > 0) { - document.getElementById('revert_template').classList.remove('disabled') - } + issueSubject.setAttribute('original_title', issueSubject.value) + if (oldSubj.trim() !== obj.issue_title.trim()) { + issueSubject.value = oldSubj + obj.issue_title + } - if (obj.related_link !== '') { - let relatedLink = document.getElementById('issue_template_related_link') + try { + if (CKEDITOR.instances.issue_description) { + CKEDITOR.instances.issue_description.setData(oldVal + obj.description) + } + } catch (e) { + // do nothing. + } + // show message just after default template loaded. + if (ns.confirmMsg && ns.shouldReplaced) { + ns.showLoadedMessage(issueDescription) + } - relatedLink.setAttribute('href', obj.related_link) - relatedLink.style.display = 'inline' - relatedLink.textContent = obj.link_title - } else { - let relatedLink = document.getElementById('issue_template_related_link') - relatedLink.style.display = 'none' - } + if (originalSubject.textContent.length > 0) { + document.getElementById('revert_template').classList.remove('disabled') + } - ns.addCheckList(obj) - ns.builtin_fields(obj) - }) + ns.setRelatedLink(obj) + ns.addCheckList(obj) + ns.builtinFields(obj) + ns.confirmToReplace = true }, - confirmToReplaceMsg: () => { - var ns = this + confirmToReplaceContent: function (obj) { + let ns = this let dialog = document.getElementById('issue_template_confirm_to_replace_dialog') dialog.style.visibility = 'visible' dialog.classList.add('active') @@ -215,7 +211,7 @@ ISSUE_TEMPLATE.prototype = { document.cookie = 'issue_template_confirm_to_replace_hide_dialog=0' } dialog.classList.remove('active') - ns.load_template(false) + ns.replaceTemplateValue(obj) }) document.getElementById('overwrite_no').addEventListener('click', () => { @@ -233,16 +229,17 @@ ISSUE_TEMPLATE.prototype = { dialog.classList.remove('active') }) }, - show_loaded_message: (confirmMsg, target) => { + showLoadedMessage: function (target) { + let ns = this // in app/views/issue_templates/_issue_select_form.html.erb let templateStatusArea = document.getElementById('template_status-area') - if (templateStatusArea === null) return false + if (templateStatusArea == null) return false if (document.querySelector('div.flash_message')) { document.querySelector('div.flash_message').remove() } let messageElement = document.createElement('div') - messageElement.innerHTML = confirmMsg + messageElement.innerHTML = ns.confirmMsg messageElement.classList.add('flash_message') messageElement.classList.add('fadeout') @@ -257,8 +254,14 @@ ISSUE_TEMPLATE.prototype = { } return '' }, - set_pulldown: function (tracker) { - var ns = this + setPulldown: function (tracker) { + let ns = this + let params = { issue_tracker_id: tracker } + let pullDownProject = document.getElementById('issue_project_id') + if (pullDownProject) { + params.issue_project_id = pullDownProject.value + } + fetch(ns.pulldownUrl, { method: 'POST', @@ -267,9 +270,7 @@ ISSUE_TEMPLATE.prototype = { 'Content-Type': 'application/json', 'X-CSRF-Token': ns.getCsrfToken() }, - body: JSON.stringify({ - issue_tracker_id: tracker - }) + body: JSON.stringify(params) }) .then((response) => { return response.text() @@ -279,7 +280,7 @@ ISSUE_TEMPLATE.prototype = { let length = document.querySelectorAll('#issue_template > optgroup > option').length if (length < 1) { document.getElementById('template_area').style.display = 'none' - if (ns.isTriggeredBy !== undefined && this.isTriggeredBy === 'issue_tracker_id') { + if (ns.isTriggeredBy != null && this.isTriggeredBy === 'issue_tracker_id') { if (document.querySelectorAll('#issue-form.new_issue').length > 0 && ns.should_replaced === true) { if (typeof templateNS !== 'undefined') { ns.eraseSubjectAndDescription() @@ -295,7 +296,7 @@ ISSUE_TEMPLATE.prototype = { }, addCheckList: function (obj) { let list = obj.checklist - if (list === undefined) return false + if (list == null) return false let checklistForm = document.getElementById('checklist_form') if (!checklistForm) return @@ -314,21 +315,31 @@ ISSUE_TEMPLATE.prototype = { console.log(`NOTE: Checklist could not be applied due to this error. ${e.message} : ${e.message}`) } }, - escapeHTML: (val) => { + setRelatedLink: function (obj) { + let relatedLink = document.getElementById('issue_template_related_link') + if (obj.related_link != null && obj.related_link !== '') { + relatedLink.setAttribute('href', obj.related_link) + relatedLink.style.display = 'inline' + relatedLink.textContent = obj.link_title + } else { + relatedLink.style.display = 'none' + } + }, + escapeHTML: function (val) { const div = document.createElement('div') div.textContent = val return div.textContent }, - unescapeHTML: (val) => { + unescapeHTML: function (val) { const div = document.createElement('div') div.innerHTML = val return div.innerHTML }, - replaceCkeContent: () => { + replaceCkeContent: function () { let element = document.getElementById('issue_description') return CKEDITOR.instances.issue_description.setData(element.value) }, - hideOverwiteConfirm: () => { + hideOverwiteConfirm: function () { let cookieArray = [] if (document.cookie !== '') { let tmp = document.cookie.split('; ') @@ -338,16 +349,16 @@ ISSUE_TEMPLATE.prototype = { } } let confirmationCookie = cookieArray['issue_template_confirm_to_replace_hide_dialog'] - if (confirmationCookie === undefined || parseInt(confirmationCookie) === 0) { + if (confirmationCookie == null || parseInt(confirmationCookie) === 0) { return false } return true }, // support built-in field update - builtin_fields: (template) => { - var ns = this + builtinFields: function (template) { + let ns = this let builtinFieldsJson = template.builtin_fields_json - if (builtinFieldsJson === undefined) return false + if (builtinFieldsJson == null) return false try { Object.keys(builtinFieldsJson).forEach(function (key) { @@ -363,7 +374,7 @@ ISSUE_TEMPLATE.prototype = { return ns.updateFieldValues(elements, value) } } - if (element === null) { + if (element == null) { return } ns.updateFieldValue(element, value) @@ -372,7 +383,7 @@ ISSUE_TEMPLATE.prototype = { console.log(`NOTE: Builtin / custom fields could not be applied due to this error. ${e.message} : ${e.message}`) } }, - updateFieldValue: (element, value) => { + updateFieldValue: function (element, value) { // In case field is a select element, scans its option values and marked 'selected'. if (element.tagName.toLowerCase() === 'select') { let values = [] @@ -393,11 +404,12 @@ ISSUE_TEMPLATE.prototype = { element.value = value } }, - updateFieldValues: (elements, value) => { + updateFieldValues: function (elements, value) { + let ns = this for (let i = 0; i < elements.length; i++) { let element = elements[i] if (element.tagName.toLowerCase() === 'select') { - return templateNS.updateFieldValue(element, value) + return ns.updateFieldValue(element, value) } if (element.value === value) { if (element.tagName.toLowerCase() === 'input') { @@ -414,7 +426,7 @@ ISSUE_TEMPLATE.prototype = { } } }, - updateTemplateSelect: (event) => { + updateTemplateSelect: function (event) { let link = event.target let optionId = link.getAttribute('data-issue-template-id') let optionSelector = '#issue_template > optgroup > option[value="' + optionId + '"]' @@ -427,7 +439,7 @@ ISSUE_TEMPLATE.prototype = { let changeEvent = new Event('change') document.getElementById('issue_template').dispatchEvent(changeEvent) }, - filterTemplate: (event) => { + filterTemplate: function (event) { let cols = document.getElementsByClassName('template_data') let searchWord = event.target.value let reg = new RegExp(searchWord, 'gi') @@ -440,7 +452,7 @@ ISSUE_TEMPLATE.prototype = { } } }, - changeTemplatePlace: () => { + changeTemplatePlace: function () { if (document.querySelector('div.flash_message')) { document.querySelector('div.flash_message').remove() } @@ -464,7 +476,7 @@ if (!Element.prototype.closest) { do { if (el.matches(s)) return el el = el.parentElement || el.parentNode - } while (el !== null && el.nodeType === 1) + } while (el != null && el.nodeType === 1) return null } } @@ -490,7 +502,7 @@ document.onreadystatechange = () => { let element = templateHelps[i] element.addEventListener('mouseenter', (event) => { let contentId = event.target.getAttribute('data-tooltip-content') - if (contentId === null) return + if (contentId == null) return let target = event.target.getAttribute('data-tooltip-area') let obj = document.getElementById(target) @@ -501,7 +513,7 @@ document.onreadystatechange = () => { }) element.addEventListener('mouseleave', (event) => { let contentId = event.target.getAttribute('data-tooltip-content') - if (contentId === null) return + if (contentId == null) return let target = event.target.getAttribute('data-tooltip-area') let obj = document.getElementById(target) @@ -544,3 +556,106 @@ document.onreadystatechange = () => { } } } + +// ------- fot NoteTemplate + +function NOTE_TEMPLATE(config) { + this.baseElementId = config.baseElementId + this.baseTemplateListUrl = config.baseTemplateListUrl + this.baseTrackerId = config.baseTrackerId + this.baseProjectId = config.baseProjectId + this.loadNoteTemplateUrl = config.loadNoteTemplateUrl +} + +NOTE_TEMPLATE.prototype = { + setNoteDescription: function (target, value, container) { + let element = document.getElementById(target) + if (element.value.length === 0) { + element.value = value + } else { + element.value += '\n\n' + value + } + element.focus() + container.style.display = 'none' + + try { + if (CKEDITOR.instances.issue_notes) { + CKEDITOR.instances.issue_notes.setData(value) + CKEDITOR.instances.issue_notes.focus() + } + } catch (e) { + // do nothing. + } + }, + applyNoteTemplate: function (targetElement) { + let ns = this + let templateId = targetElement.dataset.noteTemplateId + let projectId = document.getElementById('issue_project_id') + let loadUrl = targetElement.dataset.noteTemplateLoadUrl + + let JSONdata = { + note_template: { note_template_id: templateId } + } + + if (targetElement.classList.contains('template-global')) { + JSONdata.note_template.template_type = 'global' + JSONdata.note_template.project_id = ns.baseProjectId + if (projectId && projectId.value) { + JSONdata.note_template.project_id = projectId.value + } + } + + let token = document.querySelector('#issue-form input[name="authenticity_token"]') + let req = new XMLHttpRequest() + req.onreadystatechange = function() { + let container = targetElement.closest('div.overlay') + let target = container.id.replace('template_', '') + target = target.replace('_dialog', '') + if (req.readyState === 4) { + if (req.status === 200 || req.status === 304) { + let value = JSON.parse(req.responseText) + ns.setNoteDescription(target, value.note_template.description, container) + } + } + } + req.open('POST', loadUrl, true) + if (token) { + req.setRequestHeader('X-CSRF-Token', token.value) + } + req.setRequestHeader('Content-Type', 'application/json') + req.send(JSON.stringify(JSONdata)) + }, + changeNoteTemplateList: function (elementId) { + let ns = this + let token = document.querySelectorAll('#issue-form input[name="authenticity_token"]') + + let projectId = document.getElementById('issue_project_id') + let trackerId = document.getElementById('issue_tracker_id') + let templateListUrl = ns.baseTemplateListUrl + if (trackerId != null && projectId != null) { + templateListUrl += '?tracker_id=' + trackerId.value + templateListUrl += '&project_id=' + projectId.value + } else { + templateListUrl += '?tracker_id=' + ns.baseTrackerId + '&project_id=' + ns.baseProjectId + } + + let req = new XMLHttpRequest(); + req.onreadystatechange = function() { + if (req.readyState === 4) { + if (req.status === 200 || req.status === 304) { + let value = req.responseText + // replace here! + let dialog = document.getElementById(`${elementId}_dialog`) + let target = document.querySelector(`#${elementId}_dialog .popup .filtered_templates_list`) + target.innerHTML = value + dialog.style = 'display: block;' + } + } + } + req.open('GET', templateListUrl, true) + if (token) { + req.setRequestHeader('X-CSRF-Token', token.value) + } + req.send() + } +} diff --git a/assets/stylesheets/issue_templates.css b/assets/stylesheets/issue_templates.css index ce52146f..6dcbcfbb 100644 --- a/assets/stylesheets/issue_templates.css +++ b/assets/stylesheets/issue_templates.css @@ -181,7 +181,7 @@ td.template_title { } .filtered_templates_list { - padding-top: 10px; + padding-top: 2px; } select.issue_template { @@ -388,7 +388,7 @@ a.icon-help:hover .tooltip-area { width: auto; background-color: #c6d9ec; padding: 6px; - margin-bottom: 20px; + margin-bottom: 2px; } .overlay .cancel { @@ -398,6 +398,11 @@ a.icon-help:hover .tooltip-area { cursor: default; } +.overlay .template_search_filter_wrapper { + margin-top: 8px; + margin-bottom: 8px; +} + /*--------- for Flash message --------*/ .fadeout { animation : fadeOut 5s; diff --git a/spec/features/issue_template_spec.rb b/spec/features/issue_template_spec.rb index 13bbe676..38376dae 100644 --- a/spec/features/issue_template_spec.rb +++ b/spec/features/issue_template_spec.rb @@ -163,6 +163,8 @@ context 'have subproject' do background do + template_setting = IssueTemplateSetting.find_or_create(1) + template_setting.inherit_templates = true sub_project = Project.find(3) sub_project.inherit_members = true sub_project.enabled_modules << EnabledModule.new(name: 'issue_templates') @@ -173,6 +175,8 @@ scenario 'Select sub project then template for subproject is shown' do sub_project = page.find('#issue_project_id > option[value="3"]') + + expect(page).to have_selector('#issue_template > optgroup > option', count: 3) template_option = page.find('#issue_template > optgroup > option:nth-child(1)') expect(template_option.text).to eq issue_templates.first.title diff --git a/spec/features/update_issue_spec.rb b/spec/features/update_issue_spec.rb index 6d173f71..9f35c14d 100644 --- a/spec/features/update_issue_spec.rb +++ b/spec/features/update_issue_spec.rb @@ -103,8 +103,6 @@ Setting.send 'plugin_redmine_issue_templates=', 'apply_global_template_to_all_projects' => 'true' end - given(:template_rows) { page.find('div#template_issue_notes_dialog table > tbody') } - scenario 'One Global template for note' do visit_update_issue(user) issue = Issue.last @@ -116,6 +114,8 @@ page.find('a#link_template_issue_notes_dialog').click wait_for_ajax + template_rows = page.find('div#template_issue_notes_dialog table > tbody') + expect(page).to have_selector('div#template_issue_notes_dialog') expect(template_rows).to have_selector('tr:first-child > td:nth-child(3) > a.template-global') end From 6884a65e6d33148c2731a882b358672d63d0fd39 Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 21 Jun 2020 12:24:34 +0900 Subject: [PATCH 08/14] Fix style. --- app/views/issue_templates/index.html.erb | 6 +++--- app/views/note_templates/index.html.erb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/issue_templates/index.html.erb b/app/views/issue_templates/index.html.erb index f9b697c3..ee786420 100644 --- a/app/views/issue_templates/index.html.erb +++ b/app/views/issue_templates/index.html.erb @@ -23,7 +23,7 @@

<%= tracker.name %>

<%= non_project_tracker_msg(project_tracker?(tracker, @project)) %> -
#
+
@@ -99,7 +99,7 @@

<%=h "#{l(:label_inherited_templates)}" %>

-
#
+
@@ -180,7 +180,7 @@ <% end %> <%= l(:only_admin_can_associate_global_template) %> -
#
+
diff --git a/app/views/note_templates/index.html.erb b/app/views/note_templates/index.html.erb index 6a474cb0..9f3a0d27 100644 --- a/app/views/note_templates/index.html.erb +++ b/app/views/note_templates/index.html.erb @@ -22,7 +22,7 @@

<%= tracker.name %>

-
#
+
From 89d413b89396d69dcacad2d819d8930db57961ef Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 21 Jun 2020 16:05:01 +0900 Subject: [PATCH 09/14] Add feature spec for builtin-field. --- spec/features/issue_template_spec.rb | 36 +++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/spec/features/issue_template_spec.rb b/spec/features/issue_template_spec.rb index 38376dae..00c7e121 100644 --- a/spec/features/issue_template_spec.rb +++ b/spec/features/issue_template_spec.rb @@ -21,7 +21,8 @@ :issue_statuses, :trackers, :projects_trackers, - :enabled_modules + :enabled_modules, + :enumerations given(:role) { Role.find(1) } after do @@ -292,4 +293,37 @@ expect(issue_subject.value).to eq 'Test for revert subject' end end + + # builtin fields test + feature 'Can apply value to the builtin field' do + given(:priority_name) { IssuePriority.active.sample.name } + given(:builtin_fields_json_value) do + { + "issue_priority_id": priority_name, + "issue_estimated_hours": 5 + } + end + given(:expected_title) { 'Sample Title for rspec' } + given!(:enabled_module) { FactoryBot.create(:enabled_module) } + given!(:issue_templates) do + FactoryBot.create(:issue_template, title: expected_title, + project_id: 1, tracker_id: 1, builtin_fields_json: builtin_fields_json_value) + end + background do + # enable_builtin_fields + Setting.send 'plugin_redmine_issue_templates=', 'enable_builtin_fields' => 'true' + + assign_template_priv(role, add_permission: :show_issue_templates) + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issues/new' + + select expected_title, from: 'issue_template' + sleep(0.2) + end + + scenario 'Builtin fields are filled' do + expect(page).to have_select('issue[priority_id]', selected: priority_name) + expect(page.find('#issue_estimated_hours').value).to eq '5' + end + end end From 1fe57ee224b36c2d41046e5be05d90b02d781402 Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 21 Jun 2020 16:54:26 +0900 Subject: [PATCH 10/14] Add simple spec to confirm Vue's working. --- app/views/issue_templates/_form.html.erb | 6 +++--- spec/features/issue_template_spec.rb | 27 ++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/app/views/issue_templates/_form.html.erb b/app/views/issue_templates/_form.html.erb index 74a24c2d..1df0b527 100644 --- a/app/views/issue_templates/_form.html.erb +++ b/app/views/issue_templates/_form.html.erb @@ -60,7 +60,7 @@

- @@ -73,7 +73,7 @@

- + @@ -93,7 +93,7 @@ diff --git a/spec/features/issue_template_spec.rb b/spec/features/issue_template_spec.rb index 00c7e121..01638c38 100644 --- a/spec/features/issue_template_spec.rb +++ b/spec/features/issue_template_spec.rb @@ -79,9 +79,10 @@ feature 'create template' do given!(:enabled_module) { FactoryBot.create(:enabled_module) } + given(:issue_template_title) { page.find('#issue_template_title') } + given(:issue_template_description) { page.find('#issue_template_description') } + context 'When user has priv to issue template' do - given(:issue_template_title) { page.find('#issue_template_title') } - given(:issue_template_description) { page.find('#issue_template_description') } given(:create_button) { page.find('#issue_template-form > input[type="submit"]') } given(:error_message) { page.find('#errorExplanation') } background do @@ -99,6 +100,28 @@ expect(error_message).to have_content('Title cannot be blank') end end + + # enable buildin-fields + context 'Setting "enable_builtin_fields" is true' do + background do + # enable_builtin_fields + Setting.send 'plugin_redmine_issue_templates=', 'enable_builtin_fields' => 'true' + assign_template_priv(role, add_permission: :edit_issue_templates) + log_user('jsmith', 'jsmith') + visit '/projects/ecookbook/issue_templates/new' + end + + scenario 'form for builtin_fields are shown' do + select 'Bug', from: 'issue_template[tracker_id]' + + expect(page).to have_selector('div#json_generator') + expect(page).to have_selector('select#field_selector') + + select 'Priority', from: 'field_selector' + + expect(page).to have_select('Value', options: IssuePriority.active.pluck(:name)) + end + end end feature 'Template feature at new issue screen' do From 955cfe7bd2146380a3146114a81e8e85255ece07 Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 21 Jun 2020 21:32:00 +0900 Subject: [PATCH 11/14] Update the information for 0.3-stable branch. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 64f248b2..0f41b3b9 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Plugin to generate and use issue templates for each project to assist issue creation. The latest version 1.0.x **is not compatible with IE11**. (Related: #310) -Please use version 0.3.7 or **[0.3-stable](https://github.com/akiko-pusu/redmine_issue_templates/tree/0.3-stable) branch** (uing jQuery version) as a stable release for Redmine4.x. +Please use version 0.3.8 or **[0.3-stable](https://github.com/akiko-pusu/redmine_issue_templates/tree/0.3-stable) branch** (uing jQuery version) as a stable release for Redmine4.x. ## Repository @@ -143,7 +143,7 @@ Thank you for the valuable information and feedback, @AlUser71! ### 1.0.0 RESTRICTION: This version **is not compatible with IE11**. (Related: #310) -Please use version **0.3.7** or **[0.3-stable](https://github.com/akiko-pusu/redmine_issue_templates/tree/0.3-stable) branch** (uing jQuery version) if you need to support IE11. +Please use version **0.3.8** or **[0.3-stable](https://github.com/akiko-pusu/redmine_issue_templates/tree/0.3-stable) branch** (uing jQuery version) if you need to support IE11. NOTE: **Migration is required**. Since ``Support Built-In / Custom Fields`` is an experimental feature, please **be careful** if you hope to try it. From 2f67b53adf3142fe56e6efa3fe95e437ede00540 Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 21 Jun 2020 23:00:01 +0900 Subject: [PATCH 12/14] Change to ns. --- assets/javascripts/issue_templates.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/javascripts/issue_templates.js b/assets/javascripts/issue_templates.js index f9846760..6239944d 100644 --- a/assets/javascripts/issue_templates.js +++ b/assets/javascripts/issue_templates.js @@ -62,17 +62,17 @@ ISSUE_TEMPLATE.prototype = { let issueDescription = document.getElementById('issue_description') let oldDescription = document.getElementById('original_description') - let templateNS = window.templateNS + let ns = this - issueSubject.value = templateNS.escapeHTML(oldSubject.textContent) + issueSubject.value = ns.escapeHTML(oldSubject.textContent) if (issueDescription != null) { - issueDescription.value = templateNS.escapeHTML(oldDescription.textContent) + issueDescription.value = ns.escapeHTML(oldDescription.textContent) } try { if (CKEDITOR.instances.issue_description) { - CKEDITOR.instances.issue_description.setData(templateNS.escapeHTML(oldDescription.text())) + CKEDITOR.instances.issue_description.setData(ns.escapeHTML(oldDescription.text())) } } catch (e) { // do nothing. @@ -282,7 +282,7 @@ ISSUE_TEMPLATE.prototype = { document.getElementById('template_area').style.display = 'none' if (ns.isTriggeredBy != null && this.isTriggeredBy === 'issue_tracker_id') { if (document.querySelectorAll('#issue-form.new_issue').length > 0 && ns.should_replaced === true) { - if (typeof templateNS !== 'undefined') { + if (typeof ns !== 'undefined') { ns.eraseSubjectAndDescription() } } From 4ba5f32b554265d4317bd055f1819d9bc1105057 Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 21 Jun 2020 23:29:37 +0900 Subject: [PATCH 13/14] Add use strict statement. --- assets/javascripts/issue_templates.js | 2 ++ assets/javascripts/template_checklists.js | 1 + assets/javascripts/template_fields.js | 1 + 3 files changed, 4 insertions(+) diff --git a/assets/javascripts/issue_templates.js b/assets/javascripts/issue_templates.js index 6239944d..093f1a11 100644 --- a/assets/javascripts/issue_templates.js +++ b/assets/javascripts/issue_templates.js @@ -7,6 +7,8 @@ // For namespace setting. // var ISSUE_TEMPLATE = ISSUE_TEMPLATE || function () {} +'use strict' + function ISSUE_TEMPLATE(config) { this.pulldownUrl = config.pulldownUrl this.loadUrl = config.loadUrl diff --git a/assets/javascripts/template_checklists.js b/assets/javascripts/template_checklists.js index 7f547e72..0924165d 100644 --- a/assets/javascripts/template_checklists.js +++ b/assets/javascripts/template_checklists.js @@ -1,3 +1,4 @@ +'use strict' const removeCheckList = (obj) => { let target = obj.closest('li') target.remove() diff --git a/assets/javascripts/template_fields.js b/assets/javascripts/template_fields.js index 5df12f34..2d6fed48 100644 --- a/assets/javascripts/template_fields.js +++ b/assets/javascripts/template_fields.js @@ -1,4 +1,5 @@ // This JS is used only when create / edit template. (Using Vue.js) +'use strict' const vm = new Vue({ el: '#json_generator', data: { From 03335276d59a281fdfe294ee3d90ca970b75d649 Mon Sep 17 00:00:00 2001 From: Akiko Takano Date: Sun, 21 Jun 2020 23:42:49 +0900 Subject: [PATCH 14/14] Update version to 1.0.3. --- README.md | 9 +++++++++ init.rb | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f41b3b9..24ab398a 100644 --- a/README.md +++ b/README.md @@ -112,6 +112,15 @@ If you have any requests, bug reports, please use GitHub issues.

#