diff --git a/README.md b/README.md index 1caa06f..1cd8169 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,20 @@ # ✨ redmine_issue_dynamic_edit - -Add new elements on detailed issue page to **dynamically update issue's attributes and custom fields**, directly in the details block of the issue **without any page refresh** (*JIRA style*). +Add new interactive elements to the issue detail page to **dynamically update an issue’s attributes and custom fields**, directly within the details block, and **without any page refresh** (*JIRA-style inline editing*). -### 🔴 What info you should provide when opening an issue ->Please list your installed plugins and the Redmine version you use. Note that I can't fix every issue when you have conflict with an other plugin that also edit the page. +### 🔴 What to include when opening an issue +>Please list: +>- The Redmine version you use +>- All installed plugins > ->This plugin use JS a lot. Check your JS console from your web browser ( [HowTo](https://webmasters.stackexchange.com/a/77337) ) and try again to reproduce your issue. You'll see some information about what goes wrong. +>Conflicts may occur if another plugin modifies the same page elements. > ->Copy and paste the result that appears in your console in the Github issue and expand all possible object (error data for example). With this data, we can look if there's a problem with the ajax call the plugin performs to update the issue or if there's any JS error. +>This plugin uses JavaScript extensively. +> +>Open your browser’s JS console ([HowTo](https://webmasters.stackexchange.com/a/77337)), reproduce the problem, and review the console output. +>Copy and paste the result into your GitHub issue and expand any relevant objects (for example, error data). +> +>These details will help identify whether the problem comes from an AJAX request or a JavaScript error. ### 🔎 Example @@ -16,27 +22,27 @@ Add new elements on detailed issue page to **dynamically update issue's attribut ### 📦 Installation -* If you update the plugin, be sure to save your configuration modification (`assets/javascripts/issue_dynamic_edit_configuration_file.js`) in a safe place to set them back after the update -* Clone repo into plugins directory : `git clone https://github.com/Ilogeek/redmine_issue_dynamic_edit.git` (be sure that the parent folder is called `redmine_issue_dynamic_edit`) +* Clone the repository into your Redmine `plugins` directory : `git clone https://github.com/Ilogeek/redmine_issue_dynamic_edit.git` (ensure the folder name is `redmine_issue_dynamic_edit`) * Restart your Redmine instance -### ⚙ Configuration (new since v 0.6.6) +### ⚙ Configuration -You can set some settings by editing the file `assets/javascripts/issue_dynamic_edit_configuration_file.js`. Inside this file you'll find different variable : -* **\_CONF\_FORCE\_HTTPS** : Will force AJAX call performed by the plugin to be done with https protocol. Use this value if you encounter some difficulties with "Mixed content" issues -* **\_CONF\_DISPLAY\_EDIT\_ICON** : Choose if hovering the details block will display all the pencil icons next to editable values or if the user has to hover every value to check if (s)he can edit it. Allowed value : `single`, `block` -* **\_CONF\_LISTENER\_TYPE\_VALUE** : Choose which action will trigger the apparition of the edition block when fired from the current value. Allowed value : `none`, `click`, `dblclick` -* **\_CONF\_LISTENER\_TYPE\_ICON** : Choose which action will trigger the apparition of the edition block when fired from the pencil icon (by default: same as **\_CONF\_LISTENER\_TYPE\_VALUE**). Allowed value : `none`, `click`, `dblclick` -* **\_CONF\_LISTENER\_TARGET** : Choose which area will trigger the apparition of the edition block -* **\_CONF\_EXCLUDED\_FIELD\_ID** : Choose which fields to exclude. They won't have the edit block and pencil. Eg: `TitleInput`, `DescriptionInput`, `statusListDropdown` ... -* **\_CONF\_CHECK\_ISSUE\_UPDATE\_CONFLICT** : Choose if you allow current user to override all modifications performed by other users while editing the issue +You can adjust the following options from Redmine’s Administration → Plugins → redmine_issue_dynamic_edit panel: +* **\_CONF\_FORCE\_HTTPS** : forces all AJAX calls to use HTTPS (recommended to avoid Mixed Content issues). +* **\_CONF\_DISPLAY\_EDIT\_ICON** : controls how pencil icons appear for editable fields — set to single (hover each field individually) or block (hovering the details block shows all icons) +* **\_CONF\_LISTENER\_TYPE\_VALUE** : defines the event type (`none`, `click`, `dblclick`) that opens an editor from the field value +* **\_CONF\_LISTENER\_TYPE\_ICON** : defines the event type for the pencil icon (defaults to the same as `_CONF_LISTENER_TYPE_VALUE`) +* **\_CONF\_LISTENER\_TARGET** : defines the clickable area that triggers editing +* **\_CONF\_EXCLUDED\_FIELD\_ID** : comma-separated list of field IDs to exclude from editing (e.g. `TitleInput`, `DescriptionInput`, `statusListDropdown`) +* **\_CONF\_CHECK\_ISSUE\_UPDATE\_CONFLICT** : if enabled, prevents overwriting changes made by other users. ### 🎨 Customization -Feel free to edit `assets/stylesheets/issue_dynamic_edit.css` to update the look of your fields depending on your current Redmine Theme. +You can customize the visual style in `assets/stylesheets/issue_dynamic_edit.css` to better match your theme. ### 🆕 Changelog +* **v 0.9.3** : Add admin panel to settings section, fixes #127 #126 #96 * **v 0.9.2** : JSToolbar fixed (#100) * **v 0.9.1** : Check version improved (avoiding update conflicts) : using [Redmine REST API](https://www.redmine.org/projects/redmine/wiki/rest_issues) and disabling check when tab is not focused (#97) * **v 0.9.0** : JS rewritten to remove jQuery code diff --git a/app/views/redmine_issue_dynamic_edit/_settings.html.erb b/app/views/redmine_issue_dynamic_edit/_settings.html.erb new file mode 100644 index 0000000..7feed25 --- /dev/null +++ b/app/views/redmine_issue_dynamic_edit/_settings.html.erb @@ -0,0 +1,48 @@ +<%= stylesheet_link_tag 'excluded_tags.css', plugin: :redmine_issue_dynamic_edit %> + +
+ + <%= select_tag 'settings[display_edit_icon]', options_for_select([[t('option_display_edit_icon_single'), 'single'], [t('option_display_edit_icon_multiple'), 'block']], @settings[:display_edit_icon]) %> + <%= t('settings_display_edit_icon_explanations') %> +
+ ++ + <%= select_tag 'settings[listener_type_value]', options_for_select([['Click','click'], ['Double click','dblclick'], ['None','none']], @settings[:listener_type_value]) %> +
+ ++ + <%= select_tag 'settings[listener_type_icon]', options_for_select([['Click','click'], ['Double click','dblclick'], ['None','none']], @settings[:listener_type_icon]) %> +
+ ++ + <%= select_tag 'settings[listener_target]', options_for_select([['Value','value'], ['Pencil icon','iconEdit'], ['Label','label'], ['All','all']], @settings[:listener_target]) %> + +
+ ++ + <%= hidden_field_tag 'settings[excluded_field_id]', @settings[:excluded_field_id], id: 'settings_excluded_field_id' %> + + <%= t('settings_excluded_field_id_explanation') %> +
+ ++ + <%= hidden_field_tag 'settings[check_issue_update_conflict]', '0' %> + <%= check_box_tag 'settings[check_issue_update_conflict]', '1', @settings[:check_issue_update_conflict].to_s == 'true' || @settings[:check_issue_update_conflict].to_s == '1' %> + <%= t('settings_check_issue_update_conflict_explanation') %> +
+ ++ + <%= hidden_field_tag 'settings[force_https]', '0' %> + <%= check_box_tag 'settings[force_https]', '1', @settings[:force_https].to_s == 'true' || @settings[:force_https].to_s == '1' %> + <%= t('settings_force_https_explanation') %> +
+ +<%= javascript_include_tag 'excluded_tags.js', plugin: :redmine_issue_dynamic_edit %> diff --git a/assets/javascripts/excluded_tags.js b/assets/javascripts/excluded_tags.js new file mode 100644 index 0000000..421bdc9 --- /dev/null +++ b/assets/javascripts/excluded_tags.js @@ -0,0 +1,52 @@ +(function(){ + function createTag(text){ + const tag = document.createElement('span'); + tag.className = 'excluded-tag'; + tag.textContent = text; + const rem = document.createElement('button'); + rem.type = 'button'; + rem.className = 'remove-tag'; + rem.innerHTML = '✖'; + rem.addEventListener('click', function(){ + tag.parentNode.removeChild(tag); + syncInput(); + }); + tag.appendChild(rem); + return tag; + } + + function syncInput(){ + const container = document.getElementById('excluded_tags_container'); + const input = document.getElementById('settings_excluded_field_id'); + if(!input) return; + const values = Array.from(container.querySelectorAll('.excluded-tag')).map(t => t.firstChild.textContent.trim()); + input.value = values.join(','); + } + + function init(){ + const input = document.getElementById('settings_excluded_field_id'); + const container = document.getElementById('excluded_tags_container'); + if(!input || !container) return; + + // create an input for adding + const addInput = document.createElement('input'); + addInput.type = 'text'; + addInput.className = 'add-tag-input'; + addInput.placeholder = 'Add tag and press Enter'; + addInput.addEventListener('keydown', function(e){ + if(e.key === 'Enter'){ + e.preventDefault(); + const val = addInput.value.trim(); + if(val){ container.appendChild(createTag(val)); addInput.value = ''; syncInput(); } + } + }); + container.appendChild(addInput); + + // populate from initial value + const initial = input.value ? input.value.split(',').map(s=>s.trim()).filter(Boolean) : []; + initial.forEach(function(v){ container.appendChild(createTag(v)); }); + + } + + document.addEventListener('DOMContentLoaded', init); +})(); \ No newline at end of file diff --git a/assets/javascripts/issue_dynamic_edit.js b/assets/javascripts/issue_dynamic_edit.js index ee70c66..4ed3f73 100644 --- a/assets/javascripts/issue_dynamic_edit.js +++ b/assets/javascripts/issue_dynamic_edit.js @@ -1,15 +1,15 @@ /* * OPTIONS DEFINED FROM CONFIGURATION FILE */ -var _CONF_FORCE_HTTPS = _CONF_FORCE_HTTPS || false; -var _CONF_DISPLAY_EDIT_ICON = _CONF_DISPLAY_EDIT_ICON || "single"; -var _CONF_LISTENER_TYPE_VALUE = _CONF_LISTENER_TYPE_VALUE || "click"; -var _CONF_LISTENER_TYPE_ICON = _CONF_LISTENER_TYPE_ICON || "none"; -var _CONF_LISTENER_TARGET = _CONF_LISTENER_TARGET || "value"; -var _CONF_EXCLUDED_FIELD_ID = _CONF_EXCLUDED_FIELD_ID || []; -var _CONF_CHECK_ISSUE_UPDATE_CONFLICT = _CONF_CHECK_ISSUE_UPDATE_CONFLICT || false; +if (typeof window._CONF_FORCE_HTTPS === 'undefined') var _CONF_FORCE_HTTPS = false; +if (typeof window._CONF_DISPLAY_EDIT_ICON === 'undefined') var _CONF_DISPLAY_EDIT_ICON = "single"; +if (typeof window._CONF_LISTENER_TYPE_VALUE === 'undefined') var _CONF_LISTENER_TYPE_VALUE = "click"; +if (typeof window._CONF_LISTENER_TYPE_ICON === 'undefined') var _CONF_LISTENER_TYPE_ICON = "none"; +if (typeof window._CONF_LISTENER_TARGET === 'undefined') var _CONF_LISTENER_TARGET = "value"; +if (typeof window._CONF_EXCLUDED_FIELD_ID === 'undefined') var _CONF_EXCLUDED_FIELD_ID = []; +if (typeof window._CONF_CHECK_ISSUE_UPDATE_CONFLICT === 'undefined') var _CONF_CHECK_ISSUE_UPDATE_CONFLICT = false; -_CONF_LISTENER_TARGET = _CONF_LISTENER_TARGET === "all" ? "*" : _CONF_LISTENER_TARGET; +_CONF_LISTENER_TARGET = _CONF_LISTENER_TARGET === "all" ? "" : " ." + _CONF_LISTENER_TARGET; /* * SVG ICONS @@ -78,7 +78,6 @@ const getEditFormHTML = function(attribute){ formElement = document.querySelector('#issue_custom_field_values_' + CF_ID + '_blank'); if(formElement){ formElement = formElement.closest('p'); - formElement.removeChild(formElement.querySelector('label')); is_file = CF_ID; } else { /* Is it a checkbox/radio group ? */ @@ -90,6 +89,9 @@ const getEditFormHTML = function(attribute){ if(formElement){ const clone = formElement.cloneNode(true); + if(is_file) { + clone.removeChild(clone.querySelector('label')); + } if(clone.matches('select') && !clone.hasAttribute('multiple')) { clone.addEventListener('change', function(e){ sendData([{"name" : clone.getAttribute('name'), "value" : clone.value}]); @@ -197,24 +199,22 @@ const cloneEditForm = function(){ /* Perform action on .value (display edit form) */ document.querySelector('body').addEventListener(_CONF_LISTENER_TYPE_VALUE, function(e){ - let is_attribute = e.target.matches('div.issue.details .attributes .attribute .' + _CONF_LISTENER_TARGET) || e.target.closest('div.issue.details .attributes .attribute .' + _CONF_LISTENER_TARGET); - let is_description = e.target.matches('div.issue.details div.description > p') || e.target.closest('div.issue.details div.description > p'); - let is_subject = e.target.matches('div.issue.details div.subject') || e.target.closest('div.issue.details div.subject'); - if(is_attribute || is_description || is_subject ){ + let container = e.target.closest('.attribute' + _CONF_LISTENER_TARGET) || e.target.closest('.description') || e.target.closest('.subject'); + if(_CONF_LISTENER_TARGET && e.target.closest('.attribute' + _CONF_LISTENER_TARGET)){ + container = e.target.closest('.attribute'); + } + if(container) { if(e.target.closest('.dynamicEditField')) return; /* We're already into a dynamic field, ignore */ - document.querySelectorAll('.dynamicEditField').forEach(function(elt){ elt.classList.remove('open'); }); + document.querySelectorAll('.dynamicEditField').forEach(function(elt){ elt.classList.remove('open'); }); if(!e.target.closest('a') && !e.target.closest('button')){ - let selector = e.target.closest('.value'); - if(is_description) selector = e.target.closest('.description'); - if(is_subject) selector = e.target.closest('.subject'); - if(selector.querySelector('.dynamicEditField')) selector.querySelector('.dynamicEditField').classList.add('open'); + let selector = container.querySelector('.dynamicEditField'); + if(selector) selector.classList.add('open') } } }); /* Perform action on .iconEdit (display edit form) */ document.querySelector('body').addEventListener(_CONF_LISTENER_TYPE_ICON, function(e){ - let is_attribute = e.target.matches('div.issue.details .attributes .attribute .' + _CONF_LISTENER_TARGET) || e.target.closest('div.issue.details .attributes .attribute .' + _CONF_LISTENER_TARGET); let is_description = e.target.matches('div.issue.details div.description > p') || e.target.closest('div.issue.details div.description > p'); let is_subject = e.target.matches('div.issue.details div.subject') || e.target.closest('div.issue.details div.subject'); if(e.target.matches('.iconEdit') || e.target.closest('.iconEdit')){ diff --git a/assets/javascripts/issue_dynamic_edit_configuration_file.js b/assets/javascripts/issue_dynamic_edit_configuration_file.js deleted file mode 100644 index 8b2c754..0000000 --- a/assets/javascripts/issue_dynamic_edit_configuration_file.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * CONFIGURATION FILE - * More info on https://github.com/Ilogeek/redmine_issue_dynamic_edit - */ - -/* - * _CONF_FORCE_HTTPS (boolean) - * Will force AJAX call performed by the plugin to be done with https protocol - * Use this value if you encounter some difficulties with "Mixed content" issues - * Allowed values : false (default), true - */ -var _CONF_FORCE_HTTPS = false; - -/* - * _CONF_DISPLAY_EDIT_ICON (string) - * Choose if hovering the details block will display all the pencil icons next to editable values or if the user has to hover every value to check if (s)he can edit it - * Allowed values : single (default), block - */ -var _CONF_DISPLAY_EDIT_ICON = "single"; - -/* - * _CONF_LISTENER_TYPE_VALUE (string) - * Choose which action will trigger the apparition of the edition block - * Allowed values : click (default), dblclick, none - */ -var _CONF_LISTENER_TYPE_VALUE = "click"; - -/* - * _CONF_LISTENER_TYPE_ICON (string) - * If different from _CONF_LISTENER_TYPE_VALUE, the action set below will trigger - * the apparition of the edition block if it comes from the pencil icon. - * Allowed values : click (default), dblclick, none - */ -var _CONF_LISTENER_TYPE_ICON = "click"; - -/* - * _CONF_LISTENER_TARGET (string) - * Choose which area will trigger the apparition of the edition block - * "value" will target the value of the attribute (+ pencil), "iconEdit" will only target the pencil icon, "label" will trigger the label attribute - * "all" will target the whole line (label + value + pencil) - * Allowed values : value (default), iconEdit, label, all - */ -var _CONF_LISTENER_TARGET = "value"; - -/* - * _CONF_EXCLUDED_FIELD_ID (string array) - * Choose which fields to exclude. They won't have the edit block and pencil - * You have to take element (input, select, textarea ...) class attribute from edit form at the bottom of the page - * Custom fields have an unique ID and this ID must be prefixed by "custom_field_values_". Eg : "custom_field_values_4" is an allowed value - * Allowed values : array of any ID selector (css). Eg : ["status", "priority", "category", "assigned_to", "done_ratio", "start_date", "custom_field_values_4"] - */ -var _CONF_EXCLUDED_FIELD_ID = []; - -/* - * _CONF_CHECK_ISSUE_UPDATE_CONFLICT (boolean) - * Choose if you allow current user to override all modifications performed by other users while editing the issue - * true : will check issue update conflict and prevent current user to update the issue without refreshing the page - * false : user will be able to update the issue no matter other modification performed (will override modification made by other) - */ -var _CONF_CHECK_ISSUE_UPDATE_CONFLICT = true; diff --git a/assets/stylesheets/excluded_tags.css b/assets/stylesheets/excluded_tags.css new file mode 100644 index 0000000..879c442 --- /dev/null +++ b/assets/stylesheets/excluded_tags.css @@ -0,0 +1,4 @@ +#excluded_tags_container{ display:flex; flex-wrap:wrap; gap:6px; } +.excluded-tag{ background:#e1e1e1; padding:0px 6px; border-radius:6px; display:inline-flex; align-items:center; gap:6px; } +.excluded-tag .remove-tag{ border:none; background:transparent; color:#900; cursor:pointer; font-weight:bold; padding:0 4px; } +.add-tag-input{ border:1px solid #ccc; padding:4px 6px; border-radius:4px; min-width:120px; } diff --git a/assets/stylesheets/issue_dynamic_edit.css b/assets/stylesheets/issue_dynamic_edit.css index 583a0e9..0546f59 100644 --- a/assets/stylesheets/issue_dynamic_edit.css +++ b/assets/stylesheets/issue_dynamic_edit.css @@ -47,7 +47,8 @@ body.controller-issues.action-show div.issue.details .iconEdit { body.controller-issues.action-show div.issue.details.showPencil .iconEdit, body.controller-issues.action-show div.issue.details .attribute:hover .iconEdit, body.controller-issues.action-show div.issue.details .description:hover .iconEdit, -body.controller-issues.action-show div.issue.details div.subject:hover .iconEdit { +body.controller-issues.action-show div.issue.details div.subject:hover .iconEdit, +body.controller-issues.action-show div.issue.details.showPencils:hover .iconEdit { opacity: 1; } diff --git a/config/locales/en.yml b/config/locales/en.yml index bb63a3e..dc03406 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2,3 +2,19 @@ en: ide_txt_notice_conflict_title : "This issue has been updated by someone else while you are editing it." ide_txt_notice_conflict_link : "Refresh the current page to get the updated data." ide_txt_notice_conflict_text : "Your current modifications will be lost." + option_visual_editor_mode_switch_tab: "Enable visual editor mode switch tab" + settings_display_edit_icon : "Display edit icon mode" + settings_display_edit_icon_explanations : "Choose if hovering the details block will display all the pencil icons next to editable values or if the user has to hover every value to check if (s)he can edit it" + option_display_edit_icon_single: "Single (hover per value)" + option_display_edit_icon_multiple: "Multiple (hover block shows all icons)" + settings_force_https: "Force HTTPS for AJAX calls" + settings_force_https_explanation: "Force XHR calls to use https protocol to avoid mixed-content errors from your JS console" + settings_listener_type_value: "Listener action for value" + settings_listener_type_icon: "Listener action for icon" + settings_listener_target: "Listener target area" + settings_listener_target_explanation: "Area targeted by the listener: value, iconEdit, label or all" + settings_excluded_field_id: "Excluded field IDs (tags)" + settings_excluded_field_id_explanation: "List of form field ids to exclude from dynamic editing. Example tags: status, priority, custom_field_values_4" + settings_excluded_field_placeholder: "Add tag and press Enter" + settings_check_issue_update_conflict: "Check issue update conflict" + settings_check_issue_update_conflict_explanation: "When enabled, plugin will check for concurrent updates and prevent overriding other users' changes" diff --git a/config/locales/es.yml b/config/locales/es.yml index 6d80698..5f74ecb 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -2,3 +2,19 @@ es: ide_txt_notice_conflict_title : "Este problema ha sido actualizado por otra persona mientras lo editaba." ide_txt_notice_conflict_link : "Actualice la página actual para obtener los datos actualizados." ide_txt_notice_conflict_text : "Sus modificaciones actuales se perderán." + option_visual_editor_mode_switch_tab: "Habilitar la pestaña de cambio de modo del editor visual" + settings_display_edit_icon : "Modo de visualización del icono de edición" + settings_display_edit_icon_explanations : "Elija si al pasar el cursor por el bloque de detalles se mostrarán todos los iconos de lápiz junto a los valores editables o si el usuario debe pasar el cursor por cada valor para comprobar si puede editarlo" + option_display_edit_icon_single: "Simple (pasar por cada valor)" + option_display_edit_icon_multiple: "Múltiple (pasar por el bloque muestra todos los iconos)" + settings_force_https: "Forzar HTTPS para llamadas AJAX" + settings_force_https_explanation: "Forzar las llamadas XHR a usar el protocolo https para evitar errores de contenido mixto" + settings_listener_type_value: "Acción del listener para el valor" + settings_listener_type_icon: "Acción del listener para el icono" + settings_listener_target: "Área objetivo del listener" + settings_listener_target_explanation: "Área objetivo por el listener: value, iconEdit, label o all" + settings_excluded_field_id: "IDs de campos excluidos (tags)" + settings_excluded_field_id_explanation: "Lista de IDs de campos del formulario para excluir de la edición dinámica. Ejemplos: status, priority, custom_field_values_4" + settings_excluded_field_placeholder: "Añade una etiqueta y presiona Enter" + settings_check_issue_update_conflict: "Verificar conflicto de actualización de incidencias" + settings_check_issue_update_conflict_explanation: "Cuando está activado, el plugin comprobará actualizaciones concurrentes y evitará sobrescribir cambios hechos por otros usuarios" diff --git a/config/locales/fr.yml b/config/locales/fr.yml index 0c51b5a..1691f61 100644 --- a/config/locales/fr.yml +++ b/config/locales/fr.yml @@ -2,3 +2,19 @@ fr: ide_txt_notice_conflict_title : "La demande a été mise à jour pendant que vous la modifiez." ide_txt_notice_conflict_link : "Rafraichissez la page pour voir une version à jour de la demande." ide_txt_notice_conflict_text : "Vos modifications actuelles seront perdues" + option_visual_editor_mode_switch_tab: "Activer l'onglet de changement de mode de l'éditeur visuel" + settings_display_edit_icon : "Mode d'affichage de l'icône d'édition" + settings_display_edit_icon_explanations : "Choisissez si le survol du bloc de détails affichera toutes les icônes crayon à côté des valeurs modifiables ou si l'utilisateur doit survoler chaque valeur pour vérifier si elle est modifiable" + option_display_edit_icon_single: "Simple (survol par valeur)" + option_display_edit_icon_multiple: "Multiple (survol du bloc affiche toutes les icônes)" + settings_force_https: "Forcer HTTPS pour les appels AJAX" + settings_force_https_explanation: "Forcer les appels XHR à utiliser le protocole https afin d'éviter les erreurs de contenu mixte dans la console JS" + settings_listener_type_value: "Action du listener pour la valeur" + settings_listener_type_icon: "Action du listener pour l'icône" + settings_listener_target: "Zone cible du listener" + settings_listener_target_explanation: "Zone visée par le listener : value, iconEdit, label ou all" + settings_excluded_field_id: "IDs de champs exclus (tags)" + settings_excluded_field_id_explanation: "Liste des IDs des champs du formulaire à exclure de l'édition dynamique. Exemples de tags : status, priority, custom_field_values_4" + settings_excluded_field_placeholder: "Ajouter un tag puis appuyer sur Entrée" + settings_check_issue_update_conflict: "Vérifier les conflits de mise à jour de la demande" + settings_check_issue_update_conflict_explanation: "Si activé, le plugin vérifie les mises à jour concurrentes et empêche d'écraser les modifications d'autres utilisateurs" diff --git a/config/locales/tr.yml b/config/locales/tr.yml index 4eb94c3..b08d928 100644 --- a/config/locales/tr.yml +++ b/config/locales/tr.yml @@ -1,4 +1,20 @@ tr: ide_txt_notice_conflict_title : "Bu uyarı, siz düzenleme yaparken başka biri tarafından düzenleme yapıldığı için alınmaktadır." ide_txt_notice_conflict_link : "Güncellenmiş verileri almak için sayfayı yenileyin." - ide_txt_notice_conflict_text : "Dikkat: Son yaptığınız değişiklikler kaybolacaktır." \ No newline at end of file + ide_txt_notice_conflict_text : "Dikkat: Son yaptığınız değişiklikler kaybolacaktır." + option_visual_editor_mode_switch_tab: "Görsel editör mod değiştirici sekmesini etkinleştir" + settings_display_edit_icon : "Düzenleme simgesi görüntüleme modu" + settings_display_edit_icon_explanations : "Detay bloğunun üzerine gelindiğinde tüm kalem simgelerinin görünmesini mi yoksa her değerin ayrı ayrı üzerine gelinerek düzenleme simgesinin görünmesini mi istediğinizi seçin" + option_display_edit_icon_single: "Tekli (her değer için fareyle üzerine gelme)" + option_display_edit_icon_multiple: "Çoklu (blok üzerine gelindiğinde tüm simgeler gösterilir)" + settings_force_https: "AJAX çağruları için HTTPS zorla" + settings_force_https_explanation: "Karışık içerik hatalarını önlemek için XHR çağrılarının https protokolünü kullanmasını zorla" + settings_listener_type_value: "Değer için dinleyici eylemi" + settings_listener_type_icon: "Simge için dinleyici eylemi" + settings_listener_target: "Dinleyici hedef alanı" + settings_listener_target_explanation: "Dinleyicinin hedeflediği alan: value, iconEdit, label veya all" + settings_excluded_field_id: "Hariç tutulacak alan kimlikleri (etiketler)" + settings_excluded_field_id_explanation: "Dinamik düzenlemeye dahil edilmeyecek form alanlarının kimlikleri. Örnek etiketler: status, priority, custom_field_values_4" + settings_excluded_field_placeholder: "Etiket ekleyin ve Enter'a basın" + settings_check_issue_update_conflict: "İşlem güncelleme çatışmasını kontrol et" + settings_check_issue_update_conflict_explanation: "Etkinleştirildiğinde, eklenti eşzamanlı güncellemeleri kontrol eder ve diğer kullanıcıların değişikliklerini ezmeyi engeller" \ No newline at end of file diff --git a/init.rb b/init.rb index 3552436..35ae919 100644 --- a/init.rb +++ b/init.rb @@ -6,7 +6,18 @@ Redmine::Plugin.register :redmine_issue_dynamic_edit do name 'Redmine Dynamic edit Issue plugin' author 'Hugo Zilliox' description 'Allows users to dynamically update issue attributes in detailed view without refreshing the page (JIRA style)' - version '0.9.2' + version '0.9.3' url 'https://github.com/ilogeek/redmine_issue_dynamic_edit' author_url 'https://hzilliox.fr' + settings default: { + 'visual_editor_mode_switch_tab' => '', + 'force_https' => false, + 'display_edit_icon' => 'single', + 'listener_type_value' => 'click', + 'listener_type_icon' => 'click', + 'listener_target' => 'value', + 'excluded_field_id' => '', + 'check_issue_update_conflict' => true + }, + partial: 'redmine_issue_dynamic_edit/settings' end diff --git a/lib/details_issue_hooks.rb b/lib/details_issue_hooks.rb index 4d21b5e..69cdacc 100644 --- a/lib/details_issue_hooks.rb +++ b/lib/details_issue_hooks.rb @@ -20,7 +20,29 @@ class DetailsIssueHooks < Redmine::Hook::ViewListener return unless current_is_detail_page(context) if User.current.allowed_to?(:edit_issues, context[:project]) - javascript_include_tag('issue_dynamic_edit_configuration_file.js', 'issue_dynamic_edit.js', :plugin => :redmine_issue_dynamic_edit) + # Inject plugin settings as safe window._CONF_* variables so client-side + # scripts can read configured values without redeclaration issues. + settings = Setting.plugin_redmine_issue_dynamic_edit || {} + force_https = settings['force_https'].to_s == '1' || settings['force_https'].to_s == 'true' + display = settings['display_edit_icon'] || 'single' + l_type_value = settings['listener_type_value'] || 'click' + l_type_icon = settings['listener_type_icon'] || 'click' + l_target = settings['listener_target'] || 'value' + excluded_raw = settings['excluded_field_id'].to_s + excluded_array = excluded_raw.split(',').map(&:strip).reject(&:empty?) + check_conflict = settings['check_issue_update_conflict'].to_s == '1' || settings['check_issue_update_conflict'].to_s == 'true' + + script = "\n" + + (script + javascript_include_tag('issue_dynamic_edit.js', :plugin => :redmine_issue_dynamic_edit)).html_safe end end