diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0515ad5..d4b9e42 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,16 +7,16 @@ on: push: branches: - main - - master + - next pull_request: branches: - main - - master + - next jobs: test: name: redmine:${{ matrix.redmine_version }} ruby:${{ matrix.ruby_version }} db:${{ matrix.db }} - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 container: image: ruby:${{ matrix.ruby_version }}-bullseye @@ -24,32 +24,28 @@ jobs: strategy: fail-fast: false matrix: - redmine_version: [4.2-stable, 5.0-stable, master] - ruby_version: ['2.6', '2.7', '3.0', '3.1', '3.2'] + redmine_version: [5.0-stable, 5.1-stable, 6.0-stable, master] + ruby_version: ['3.1', '3.2', '3.3'] db: [mysql, postgres, sqlite] # # System test takes 2~3 times longer, so limit to specific matrix combinations # # See: https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs#expanding-or-adding-matrix-configurations # include: # - system_test: true # redmine_version: master - # ruby_version: '3.1' + # ruby_version: '3.3' # db: mysql exclude: - - redmine_version: 4.2-stable - ruby_version: '3.0' - - redmine_version: 4.2-stable - ruby_version: '3.1' - - redmine_version: 4.2-stable - ruby_version: '3.2' - redmine_version: 5.0-stable ruby_version: '3.2' - - redmine_version: master - ruby_version: '2.6' + - redmine_version: 5.0-stable + ruby_version: '3.3' + - redmine_version: 5.1-stable + ruby_version: '3.3' services: mysql: image: mysql:5.7 # min - # image: mysql:8.0 # latest + # image: mysql:9.1 # latest env: MYSQL_ROOT_PASSWORD: password ports: @@ -57,7 +53,7 @@ jobs: options: --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 5 postgres: image: postgres:10 # min - # image: postgres:15 # latest + # image: postgres:17 # latest env: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres @@ -67,14 +63,14 @@ jobs: steps: - name: Checkout Redmine - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: redmine/redmine ref: ${{ matrix.redmine_version }} path: redmine - name: Checkout Plugin - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: redmine/plugins/${{ env.PLUGIN_NAME }} @@ -165,14 +161,14 @@ jobs: # For system test in plugin GOOGLE_CHROME_OPTS_ARGS: "headless,disable-gpu,no-sandbox,disable-dev-shm-usage" working-directory: redmine - run: bundle exec rake redmine:plugins:test NAME=${{ env.PLUGIN_NAME }} RUBYOPT="-W0" + run: bundle exec rails test plugins/redmine_text_blocks/test # - name: Run core tests # env: # RAILS_ENV: test # PARALLEL_WORKERS: 1 # working-directory: redmine - # run: bundle exec rake test + # run: bundle exec rails test # - name: Run core system tests # if: matrix.system_test == true @@ -180,7 +176,7 @@ jobs: # RAILS_ENV: test # GOOGLE_CHROME_OPTS_ARGS: "headless,disable-gpu,no-sandbox,disable-dev-shm-usage" # working-directory: redmine - # run: bundle exec rake test:system + # run: bundle exec rails test:system - name: Run uninstall test env: diff --git a/README.md b/README.md index a409a0a..ba55b8d 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,12 @@ This plugin adds configurable text blocks for replying to issues. ## Requirements -- Redmine >= 4.0.0 +- Redmine >= 5.0.0 ## Installation -To install Redmine text blocks plugin, download or clone this repository in your Redmine installation plugins directory! +To install Redmine text blocks plugin, download or clone this repository in your +Redmine installation plugins directory! ```sh cd path/to/plugin/directory @@ -26,9 +27,11 @@ bundle install bundle exec rake redmine:plugins:migrate ``` -After restarting Redmine, you should be able to see the Redmine Text Blocks plugin in the Plugins page. +After restarting Redmine, you should be able to see the Redmine Text Blocks +plugin in the Plugins page. -More information on installing (and uninstalling) Redmine plugins can be found in [Redmine Plugins](http://www.redmine.org/wiki/redmine/Plugins) documentation. +More information on installing (and uninstalling) Redmine plugins can be found in +[Redmine Plugins](http://www.redmine.org/wiki/redmine/Plugins) documentation. ## How to use @@ -36,16 +39,13 @@ More information on installing (and uninstalling) Redmine plugins can be found i ## Contributing and Support -The Text Blocks Project appreciates any [contributions](https://github.com/gtt-project/.github/blob/main/CONTRIBUTING.md)! Feel free to contact us for [reporting problems and support](https://github.com/gtt-project/.github/blob/main/CONTRIBUTING.md). +The Text Blocks Project appreciates any [contributions](https://github.com/gtt-project/.github/blob/main/CONTRIBUTING.md)! +Feel free to contact us for [reporting problems and support](https://github.com/gtt-project/.github/blob/main/CONTRIBUTING.md). ## Version History -- 2.0.0 Support Redmine 5.0 -- 1.2.0 Publish on GitHub -- 1.0.2 Fixes localization -- 1.0.1 Bugfix - -See [all releases](https://github.com/gtt-project/redmine_text_blocks/releases) with release notes. +See [all releases](https://github.com/gtt-project/redmine_text_blocks/releases) +with release notes. ## Authors diff --git a/app/models/text_block.rb b/app/models/text_block.rb index f6a5b7e..0232ce0 100644 --- a/app/models/text_block.rb +++ b/app/models/text_block.rb @@ -1,4 +1,4 @@ -class TextBlock < ActiveRecord::Base +class TextBlock < (defined?(ApplicationRecord) == 'constant' ? ApplicationRecord : ActiveRecord::Base) belongs_to :project has_and_belongs_to_many :issue_statuses acts_as_positioned :scope => [:project_id] diff --git a/app/views/hooks/text_blocks/_view_issues_edit_notes_bottom.html.erb b/app/views/hooks/text_blocks/_view_issues_edit_notes_bottom.html.erb index 476fa38..c027797 100644 --- a/app/views/hooks/text_blocks/_view_issues_edit_notes_bottom.html.erb +++ b/app/views/hooks/text_blocks/_view_issues_edit_notes_bottom.html.erb @@ -1,5 +1,5 @@ <% if User.current.allowed_to?(:view_text_blocks, @project) %> - <%= select_tag :text_block, text_block_options(@issue), id: 'textblock-select', style: 'display:none; border: 1px solid #e0e2e3; background: #f6f6f6; height: 24px; color: #3d454c; vertical-align: bottom; margin-left: 6px; margin-botton: 0;' %> + <%= select_tag :text_block, text_block_options(@issue), id: 'textblock-select' %> <%= javascript_include_tag 'text_blocks', plugin: 'redmine_text_blocks' %> <%= hidden_field_tag :text_block_project_id, @project.id %> <% end %> diff --git a/app/views/text_blocks/_form.html.erb b/app/views/text_blocks/_form.html.erb index 5d7d37c..b573349 100644 --- a/app/views/text_blocks/_form.html.erb +++ b/app/views/text_blocks/_form.html.erb @@ -2,7 +2,7 @@

<%= f.text_field :name, required: true, size: 25 %>

-

<%= f.text_area :text, label: l(:field_text_block_text), rows: 10 %>

+

<%= f.text_area :text, label: l(:field_text_block_text), rows: 10, class: 'wiki-edit' %>

<%= content_tag :label, l(:label_issue_status_plural) %> <%= f.collection_select :issue_status_ids, diff --git a/app/views/text_blocks/_list.html.erb b/app/views/text_blocks/_list.html.erb index 0a5b8bc..f4c4f57 100644 --- a/app/views/text_blocks/_list.html.erb +++ b/app/views/text_blocks/_list.html.erb @@ -17,21 +17,7 @@ <% end %> <%= content_for :header_tags do %> - <%= javascript_include_tag 'jquery.dotdotdot', plugin: 'redmine_text_blocks' %> <%= javascript_tag do %> - $(document).ready(function() { - $("table.list.textblocks tbody td.text div").dotdotdot({ - watch: 'window', - }); - - // make sure text is shortened when it was initially hidden and just - // becomes visible due to Redmine's tabbing in project settings: - var origShowTab = window.showTab; - window.showTab = function(name, url){ - origShowTab(name, url); - $("table.list.textblocks tbody td.text div").trigger('update.dot'); - } - }); $(function() { $("table.textblocks tbody").positionedItems(); }); diff --git a/app/views/text_blocks/_text_block.html.erb b/app/views/text_blocks/_text_block.html.erb index 4d65240..ee8908e 100644 --- a/app/views/text_blocks/_text_block.html.erb +++ b/app/views/text_blocks/_text_block.html.erb @@ -1,7 +1,7 @@ <%= link_to text_block.name, (text_block.project ? edit_project_text_block_path(text_block.project, text_block) : edit_text_block_path(text_block)) %> -

<%= textilizable text_block.text %>
-
+
<%= textilizable text_block.text %>
+
<% text_block.issue_statuses.pluck(:name).map do |s| %> <%= textilizable s %> <% end %> diff --git a/app/views/text_blocks/index.html.erb b/app/views/text_blocks/index.html.erb index 03c6a8e..6cb4669 100644 --- a/app/views/text_blocks/index.html.erb +++ b/app/views/text_blocks/index.html.erb @@ -1,5 +1,5 @@
-<%= link_to l(:label_text_block_new), new_text_block_path, class: 'icon icon-add' %> +<%= link_to (Redmine::VERSION.to_s >= '6.0.0') ? sprite_icon('add', l(:label_text_block_new)) : l(:label_text_block_new), new_text_block_path, :class => 'icon icon-add' %>

<%= l :label_text_block_plural %>

diff --git a/assets/images/icons.svg b/assets/images/icons.svg new file mode 100644 index 0000000..a7a9c11 --- /dev/null +++ b/assets/images/icons.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/javascripts/jquery.dotdotdot.js b/assets/javascripts/jquery.dotdotdot.js deleted file mode 100644 index de83ece..0000000 --- a/assets/javascripts/jquery.dotdotdot.js +++ /dev/null @@ -1,24 +0,0 @@ -;(function(root, factory) { - if (typeof define === 'function' && define.amd) { - define(['jquery'], factory); - } else if (typeof exports === 'object') { - module.exports = factory(require('jquery')); - } else { - root.jquery_dotdotdot_js = factory(root.jQuery); - } -}(this, function(jQuery) { -/* - * jQuery dotdotdot 2.0.1 - * - * Copyright (c) Fred Heusschen - * www.frebsite.nl - * - * Plugin website: - * dotdotdot.frebsite.nl - * - * Licensed under the MIT license. - * http://en.wikipedia.org/wiki/MIT_License - */ -!function(t,e){"use strict";function n(t,e,n){var r=t.children(),o=!1;t.empty();for(var i=0,d=r.length;d>i;i++){var l=r.eq(i);if(t.append(l),n&&t.append(n),a(t,e)){l.remove(),o=!0;break}n&&n.detach()}return o}function r(e,n,i,d,l){var s=!1,c="a, table, thead, tbody, tfoot, tr, col, colgroup, object, embed, param, ol, ul, dl, blockquote, select, optgroup, option, textarea, script, style",u="script, .dotdotdot-keep";return e.contents().detach().each(function(){var f=this,h=t(f);if("undefined"==typeof f)return!0;if(h.is(u))e.append(h);else{if(s)return!0;e.append(h),!l||h.is(d.after)||h.find(d.after).length||e[e.is(c)?"after":"append"](l),a(i,d)&&(s=3==f.nodeType?o(h,n,i,d,l):r(h,n,i,d,l)),s||l&&l.detach()}}),a(i,d)&&r(i,n,i,d,l),n.addClass("is-truncated"),s}function o(e,n,r,o,d){var c=e[0];if(!c)return!1;var f=s(c),h=-1!==f.indexOf(" ")?" ":" ",p="letter"==o.wrap?"":h,g=f.split(p),v=-1,w=-1,m=0,y=g.length-1;if(o.fallbackToLetter&&0===m&&0===y&&(p="",g=f.split(p),y=g.length-1),o.maxLength)f=i(f.trim().substr(0,o.maxLength),o),l(c,f);else{for(;y>=m&&(0!==m||0!==y);){var b=Math.floor((m+y)/2);if(b==w)break;w=b,l(c,g.slice(0,w+1).join(p)+o.ellipsis),r.children().each(function(){t(this).toggle().toggle()}),a(r,o)?(y=w,o.fallbackToLetter&&0===m&&0===y&&(p="",g=g[0].split(p),v=-1,w=-1,m=0,y=g.length-1)):(v=w,m=w)}if(-1==v||1===g.length&&0===g[0].length){var x=e.parent();e.detach();var T=d&&d.closest(x).length?d.length:0;if(x.contents().length>T?c=u(x.contents().eq(-1-T),n):(c=u(x,n,!0),T||x.detach()),c&&(f=i(s(c),o),l(c,f),T&&d)){var C=d.parent();t(c).parent().append(d),t.trim(C.html())||C.remove()}}else f=i(g.slice(0,v+1).join(p),o),l(c,f)}return!0}function a(t,e){return t.innerHeight()>e.maxHeight||e.maxLength&&t.text().trim().length>e.maxLength}function i(e,n){for(;t.inArray(e.slice(-1),n.lastCharacter.remove)>-1;)e=e.slice(0,-1);return t.inArray(e.slice(-1),n.lastCharacter.noEllipsis)<0&&(e+=n.ellipsis),e}function d(t){return{width:t.innerWidth(),height:t.innerHeight()}}function l(t,e){t.innerText?t.innerText=e:t.nodeValue?t.nodeValue=e:t.textContent&&(t.textContent=e)}function s(t){return t.innerText?t.innerText:t.nodeValue?t.nodeValue:t.textContent?t.textContent:""}function c(t){do t=t.previousSibling;while(t&&1!==t.nodeType&&3!==t.nodeType);return t}function u(e,n,r){var o,a=e&&e[0];if(a){if(!r){if(3===a.nodeType)return a;if(t.trim(e.text()))return u(e.contents().last(),n)}for(o=c(a);!o;){if(e=e.parent(),e.is(n)||!e.length)return!1;o=c(e[0])}if(o)return u(t(o),n)}return!1}function f(e,n){return e?"string"==typeof e?(e=t(e,n),e.length?e:!1):e.jquery?e:!1:!1}function h(t){for(var e=t.innerHeight(),n=["paddingTop","paddingBottom"],r=0,o=n.length;o>r;r++){var a=parseInt(t.css(n[r]),10);isNaN(a)&&(a=0),e-=a}return e}if(!t.fn.dotdotdot){t.fn.dotdotdot=function(e){if(0===this.length)return t.fn.dotdotdot.debug('No element found for "'+this.selector+'".'),this;if(this.length>1)return this.each(function(){t(this).dotdotdot(e)});var o=t(window),i=this;i.data("dotdotdot")&&i.trigger("destroy.dot");var l=i.contents();i.data("dotdotdot-style",i.attr("style")||""),i.css("word-wrap","break-word"),"nowrap"===i.css("white-space")&&i.css("white-space","normal"),i.bind_events=function(){return i.on("update.dot",function(e,o){switch(i.removeClass("is-truncated"),e.preventDefault(),e.stopPropagation(),typeof s.height){case"number":s.maxHeight=s.height;break;case"function":s.maxHeight=s.height.call(i[0]);break;default:s.maxHeight=h(i)}s.maxHeight+=s.tolerance,"undefined"!=typeof o&&(("string"==typeof o||"nodeType"in o&&1===o.nodeType)&&(o=t("
").append(o).contents()),o instanceof t&&(l=o)),v=i.wrapInner('
').children(),v.contents().detach().end().append(l.clone(!0)).find("br").replaceWith("
").end().css({height:"auto",width:"auto",border:"none",padding:0,margin:0});var d=!1,u=!1;return c.afterElement&&(d=c.afterElement.clone(!0),d.show(),c.afterElement.detach()),a(v,s)&&(u="children"==s.wrap?n(v,s,d):r(v,i,v,s,d)),v.replaceWith(v.contents()),v=null,t.isFunction(s.callback)&&s.callback.call(i[0],u,l),c.isTruncated=u,u}).on("isTruncated.dot",function(t,e){return t.preventDefault(),t.stopPropagation(),"function"==typeof e&&e.call(i[0],c.isTruncated),c.isTruncated}).on("originalContent.dot",function(t,e){return t.preventDefault(),t.stopPropagation(),"function"==typeof e&&e.call(i[0],l),l}).on("destroy.dot",function(t){t.preventDefault(),t.stopPropagation(),i.unwatch().unbind_events().contents().detach().end().append(l).attr("style",i.data("dotdotdot-style")||"").removeClass("is-truncated").data("dotdotdot",!1)}),i},i.unbind_events=function(){return i.off(".dot"),i},i.watch=function(){if(i.unwatch(),"window"==s.watch){var t=o.width(),e=o.height();o.on("resize.dot"+c.dotId,function(){var n=o.width(),r=o.height();t==n&&e==r&&s.windowResizeFix||(t=n,e=r,g&&clearInterval(g),g=setTimeout(function(){i.trigger("update.dot")},100))})}else u=d(i),g=setInterval(function(){if(i.is(":visible")){var t=d(i);u.width==t.width&&u.height==t.height||(i.trigger("update.dot"),u=t)}},500);return i},i.unwatch=function(){return t(window).off("resize.dot"+c.dotId),g&&clearInterval(g),i};var s=t.extend(!0,{},t.fn.dotdotdot.defaults,e),c={},u={},g=null,v=null;return s.lastCharacter.remove instanceof Array||(s.lastCharacter.remove=t.fn.dotdotdot.defaultArrays.lastCharacter.remove),s.lastCharacter.noEllipsis instanceof Array||(s.lastCharacter.noEllipsis=t.fn.dotdotdot.defaultArrays.lastCharacter.noEllipsis),c.afterElement=f(s.after,i),c.isTruncated=!1,c.dotId=p++,i.data("dotdotdot",!0).bind_events().trigger("update.dot"),s.watch&&i.watch(),i},t.fn.dotdotdot.defaults={ellipsis:"… ",wrap:"word",fallbackToLetter:!0,lastCharacter:{},tolerance:0,callback:null,after:null,height:null,watch:!1,windowResizeFix:!0,maxLength:null},t.fn.dotdotdot.defaultArrays={lastCharacter:{remove:[" "," ",",",";",".","!","?"],noEllipsis:[]}},t.fn.dotdotdot.debug=function(t){};var p=1}}(jQuery); -return true; -})); diff --git a/assets/javascripts/text_blocks.js b/assets/javascripts/text_blocks.js index d731af1..785d6c8 100644 --- a/assets/javascripts/text_blocks.js +++ b/assets/javascripts/text_blocks.js @@ -12,7 +12,6 @@ var TextBlocks = { var value = $(this).val(); if(value == '') return; - console.log(value); var fieldId = $('#textblock-select').parents().next('div.jstEditor').find('textarea').attr('id'); var field = document.getElementById(fieldId); @@ -36,7 +35,7 @@ var TextBlocks = { reload: function(e){ var projectId = null - if ($("#issue_project_id").size() > 0) { + if ($("#issue_project_id").length > 0) { projectId = $("#issue_project_id").val() } else { projectId = $("#text_block_project_id").val() diff --git a/assets/stylesheets/text_blocks.css b/assets/stylesheets/text_blocks.css index 1b544ad..9ec5b85 100644 --- a/assets/stylesheets/text_blocks.css +++ b/assets/stylesheets/text_blocks.css @@ -2,8 +2,7 @@ /* ICONS /**************************************************************/ -#admin-menu a.text-blocks { background-image: url(../images/text_signature.png);} - +.icon-text-blocks:not(:has(svg)) { background-image: url(../images/text_signature.png); } /**************************************************************/ /* TextBlocks table @@ -11,25 +10,45 @@ table.list.textblocks tbody td { vertical-align: top; } table.list.textblocks tbody td.text { text-align: left; } -table.list.textblocks tbody td.text div { height: 3.8em; } - /**************************************************************/ /* TextBlocks template selector /**************************************************************/ +select#textblock-select { + display: none; + border: 1px solid #e0e2e3; + background-color: #f7f7f7; +} + div.jstElements { display: inline-flex; } -@media all and (max-width: 899px) { +table.textblocks th { + min-width: 100px; /* to ensure columns don't get too narrow */ +} + +table.textblocks .truncated { + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + + /* Number of lines to show */ + -webkit-line-clamp: 3; + line-clamp: 3; +} + +@media all and (max-width: 1099px) { #textblock-select { - order: -1 + order: -1; + margin-right: 6px; } } -@media all and (min-width: 900px) { +@media all and (min-width: 1100px) { #textblock-select { - order: 1 + order: 1; + margin-left: 6px; } } diff --git a/config/icon_source.yml b/config/icon_source.yml new file mode 100644 index 0000000..011d81b --- /dev/null +++ b/config/icon_source.yml @@ -0,0 +1,2 @@ +- name: text-blocks + svg: template diff --git a/init.rb b/init.rb index 68795d3..3a2c2de 100644 --- a/init.rb +++ b/init.rb @@ -1,15 +1,4 @@ -require File.expand_path('../lib/redmine_text_blocks/view_hooks', __FILE__) - -if Rails.version > '6.0' && Rails.autoloaders.zeitwerk_enabled? - Rails.application.config.after_initialize do - RedmineTextBlocks.setup - end -else - require 'redmine_text_blocks' - Rails.configuration.to_prepare do - RedmineTextBlocks.setup - end -end +require_relative 'lib/redmine_text_blocks/view_hooks' Redmine::Plugin.register :redmine_text_blocks do name 'Redmine Text Blocks Plugin' @@ -17,9 +6,9 @@ author_url 'https://github.com/georepublic' url 'https://github.com/gtt-project/redmine_text_blocks' description 'Adds configurable text blocks for replying to issues' - version '2.0.1' + version '3.0.0' - requires_redmine version_or_higher: '4.0.0' + requires_redmine version_or_higher: '5.0.0' #settings default: { #}, partial: 'redmine_text_blocks/settings' @@ -35,5 +24,10 @@ menu :admin_menu, :text_blocks, { controller: 'text_blocks', action: 'index' }, - caption: :label_text_block_plural, :html => {:class => 'icon icon-text-blocks'} + caption: :label_text_block_plural, :html => {:class => 'icon icon-text-blocks'}, + :icon => 'text-blocks', :plugin => :redmine_text_blocks +end + +Rails.application.config.after_initialize do + RedmineTextBlocks.setup end