From db67da6d28db62c7e3e28afbe655f8139ca51c49 Mon Sep 17 00:00:00 2001 From: Jens Kraemer Date: Thu, 31 Aug 2017 15:36:42 +0800 Subject: [PATCH 1/5] global text block admin --- Gemfile | 1 + app/controllers/text_blocks_controller.rb | 83 +++++++++++++++++++ app/models/text_block.rb | 22 +++++ app/views/text_blocks/_form.html.erb | 10 +++ app/views/text_blocks/_text_block.html.erb | 5 ++ app/views/text_blocks/edit.html.erb | 11 +++ app/views/text_blocks/index.html.erb | 26 ++++++ app/views/text_blocks/new.html.erb | 11 +++ config/locales/en.yml | 12 +++ config/routes.rb | 6 ++ .../20170831050051_create_text_blocks.rb | 11 +++ init.rb | 33 ++++++++ lib/redmine_text_blocks.rb | 8 ++ lib/redmine_text_blocks/hooks.rb | 0 .../project_settings_tabs.rb | 21 +++++ lib/redmine_text_blocks/save_text_block.rb | 26 ++++++ test/integration/text_blocks_admin_test.rb | 49 +++++++++++ test/test_helper.rb | 2 + test/unit/text_block_test.rb | 58 +++++++++++++ 19 files changed, 395 insertions(+) create mode 100644 Gemfile create mode 100644 app/controllers/text_blocks_controller.rb create mode 100644 app/models/text_block.rb create mode 100644 app/views/text_blocks/_form.html.erb create mode 100644 app/views/text_blocks/_text_block.html.erb create mode 100644 app/views/text_blocks/edit.html.erb create mode 100644 app/views/text_blocks/index.html.erb create mode 100644 app/views/text_blocks/new.html.erb create mode 100644 config/locales/en.yml create mode 100644 config/routes.rb create mode 100644 db/migrate/20170831050051_create_text_blocks.rb create mode 100644 init.rb create mode 100644 lib/redmine_text_blocks.rb create mode 100644 lib/redmine_text_blocks/hooks.rb create mode 100644 lib/redmine_text_blocks/project_settings_tabs.rb create mode 100644 lib/redmine_text_blocks/save_text_block.rb create mode 100644 test/integration/text_blocks_admin_test.rb create mode 100644 test/test_helper.rb create mode 100644 test/unit/text_block_test.rb diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..d9ee719 --- /dev/null +++ b/Gemfile @@ -0,0 +1 @@ +gem 'immutable-struct' diff --git a/app/controllers/text_blocks_controller.rb b/app/controllers/text_blocks_controller.rb new file mode 100644 index 0000000..03afab9 --- /dev/null +++ b/app/controllers/text_blocks_controller.rb @@ -0,0 +1,83 @@ +class TextBlocksController < ApplicationController + + before_action :find_project_by_project_id + + before_action :require_admin, if: ->{ @project.nil? } + before_action :authorize, if: ->{ @project.present? } + + def index + @text_blocks = text_block_scope + end + + def edit + @text_block = find_text_block + end + + def new + @text_block = TextBlock.new + end + + def create + r = RedmineTextBlocks::SaveTextBlock.(text_block_params, + project: @project) + if r.text_block_saved? + if params[:continue] + redirect_to new_path + else + redirect_to index_path + end + else + @text_block = r.text_block + render 'new' + end + end + + def update + @text_block = find_text_block + r = RedmineTextBlocks::SaveTextBlock.(text_block_params, + text_block: @text_block) + if r.text_block_saved? + redirect_to index_path + else + render 'edit' + end + end + + def destroy + find_text_block.destroy + redirect_to index_path + end + + + private + + def new_path + @project ? new_project_text_block_path(@project) : new_text_block_path + end + def index_path + @project ? project_text_blocks_path(@project) : text_blocks_path + end + + def text_block_params + params[:text_block].permit :name, :text + end + + def find_text_block + text_block_scope.find params[:id] + end + + def find_project_by_project_id + if id = params[:project_id] + @project = Project.find id + end + end + + def text_block_scope + text_blocks = TextBlock.order(name: :asc) + if @project + text_blocks = text_blocks.where(project_id: @project.id) + end + text_blocks + end + +end diff --git a/app/models/text_block.rb b/app/models/text_block.rb new file mode 100644 index 0000000..dd483b6 --- /dev/null +++ b/app/models/text_block.rb @@ -0,0 +1,22 @@ +class TextBlock < ActiveRecord::Base + belongs_to :project + + validates :name, presence: true + validate :name_uniqueness + + + private + + def name_uniqueness + scope = TextBlock.where.not(id: id).where(name: name) + + if project_id.present? + scope = scope.where project_id: [ nil, project_id ] + end + + if scope.any? + errors.add :name, I18n.t('model.text_block.name_uniqueness') + end + end + +end diff --git a/app/views/text_blocks/_form.html.erb b/app/views/text_blocks/_form.html.erb new file mode 100644 index 0000000..cd85469 --- /dev/null +++ b/app/views/text_blocks/_form.html.erb @@ -0,0 +1,10 @@ +<%= error_messages_for 'text_block' %> + +
+

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

+

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

+
+ + + + diff --git a/app/views/text_blocks/_text_block.html.erb b/app/views/text_blocks/_text_block.html.erb new file mode 100644 index 0000000..fc48169 --- /dev/null +++ b/app/views/text_blocks/_text_block.html.erb @@ -0,0 +1,5 @@ + + <%= link_to text_block.name, edit_text_block_path(text_block) %> + <%= text_block.text %> + <%= delete_link text_block_path(text_block) %> + diff --git a/app/views/text_blocks/edit.html.erb b/app/views/text_blocks/edit.html.erb new file mode 100644 index 0000000..8563b13 --- /dev/null +++ b/app/views/text_blocks/edit.html.erb @@ -0,0 +1,11 @@ +<%= title [l(:label_text_block_plural), text_blocks_path], @text_block.name %> + +<%= labelled_form_for @text_block, url: text_block_path(@text_block), method: :patch do |f| %> + <%= render partial: 'form', locals: { f: f } %> +

+ <%= submit_tag l :button_save %> +

+<% end %> + + + diff --git a/app/views/text_blocks/index.html.erb b/app/views/text_blocks/index.html.erb new file mode 100644 index 0000000..93eb135 --- /dev/null +++ b/app/views/text_blocks/index.html.erb @@ -0,0 +1,26 @@ +
+<%= link_to l(:label_text_block_new), new_text_block_path, class: 'icon icon-add' %> +
+ +

<%= l :label_text_block_plural %>

+ +<% if @text_blocks.any? %> + + + + + + + + + + <%= render collection: @text_blocks, partial: 'text_block' %> + +
<%= l :label_text_block_name %><%= l :label_text_block_text %>
+ +<% else %> +

<%= l :label_no_data %>

+<% end %> + +<% html_title l :label_text_block_plural -%> + diff --git a/app/views/text_blocks/new.html.erb b/app/views/text_blocks/new.html.erb new file mode 100644 index 0000000..703863f --- /dev/null +++ b/app/views/text_blocks/new.html.erb @@ -0,0 +1,11 @@ +<%= title [l(:label_text_block_plural), text_blocks_path], l(:label_text_block_new) %> + +<%= labelled_form_for @text_block, url: text_blocks_path do |f| %> + <%= render partial: 'form', locals: { f: f } %> +

+ <%= submit_tag l :button_create %> + <%= submit_tag l(:button_create_and_continue), name: 'continue' %> +

+<% end %> + + diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 0000000..1d8d966 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,12 @@ +en: + + field_text_block_text: Text + + label_text_block_name: Name + label_text_block_text: Text + label_text_block_plural: Text blocks + label_text_block_new: New text block + + model: + text_block: + name_uniqueness: A text block with that name already exists. diff --git a/config/routes.rb b/config/routes.rb new file mode 100644 index 0000000..785fb4e --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,6 @@ +resources :text_blocks, only: %i(index new create edit update destroy) + +scope 'projects/:project_id' do + resources :text_blocks, only: %i(index new create edit update destroy), + as: :project_text_blocks +end diff --git a/db/migrate/20170831050051_create_text_blocks.rb b/db/migrate/20170831050051_create_text_blocks.rb new file mode 100644 index 0000000..26cecff --- /dev/null +++ b/db/migrate/20170831050051_create_text_blocks.rb @@ -0,0 +1,11 @@ +class CreateTextBlocks < ActiveRecord::Migration + def change + create_table :text_blocks do |t| + t.string :name + t.text :text + t.references :project, index: true, foreign_key: true + + t.timestamps null: false + end + end +end diff --git a/init.rb b/init.rb new file mode 100644 index 0000000..92c81ea --- /dev/null +++ b/init.rb @@ -0,0 +1,33 @@ +require 'redmine' + +Rails.configuration.to_prepare do + RedmineTextBlocks.setup +end + + +Redmine::Plugin.register :redmine_text_blocks do + name 'Redmine Text Blocks Plugin' + author 'Jens Krämer, Georepublic' + author_url 'https://hub.georepublic.net/gtt/redmine_text_blocks' + description 'Adds configurable text blocks for replying to issues' + version '0.1.0' + + requires_redmine version_or_higher: '3.4.0' + + #settings default: { + #}, partial: 'redmine_text_blocks/settings' + + project_module :text_blocks do + + permission :view_text_blocks, {}, require: :member, read: :true + permission :manage_text_blocks, { + text_blocks: %i(index new edit update create destroy) + }, require: :member + end + + menu :admin_menu, :text_blocks, + { controller: 'text_blocks', action: 'index' }, + caption: :label_text_block_plural + +end + diff --git a/lib/redmine_text_blocks.rb b/lib/redmine_text_blocks.rb new file mode 100644 index 0000000..9a8853e --- /dev/null +++ b/lib/redmine_text_blocks.rb @@ -0,0 +1,8 @@ +require 'redmine_text_blocks/hooks' + + +module RedmineTextBlocks + def self.setup + ProjectsController.send :helper, RedmineTextBlocks::ProjectSettingsTabs + end +end diff --git a/lib/redmine_text_blocks/hooks.rb b/lib/redmine_text_blocks/hooks.rb new file mode 100644 index 0000000..e69de29 diff --git a/lib/redmine_text_blocks/project_settings_tabs.rb b/lib/redmine_text_blocks/project_settings_tabs.rb new file mode 100644 index 0000000..1612f0e --- /dev/null +++ b/lib/redmine_text_blocks/project_settings_tabs.rb @@ -0,0 +1,21 @@ +module RedmineTextBlocks + + # hook into the helper method that renders the project settings tabs + module ProjectSettingsTabs + + def project_settings_tabs + super.tap do |tabs| + if User.current.allowed_to?(:manage_text_blocks, @project) + tabs << { + name: 'text_blocks', + action: :text_blocks, + partial: 'projects/settings/text_blocks', + label: :label_text_blocks + } + end + end + end + + end +end + diff --git a/lib/redmine_text_blocks/save_text_block.rb b/lib/redmine_text_blocks/save_text_block.rb new file mode 100644 index 0000000..1d15bb9 --- /dev/null +++ b/lib/redmine_text_blocks/save_text_block.rb @@ -0,0 +1,26 @@ +module RedmineTextBlocks + class SaveTextBlock + + Result = ImmutableStruct.new :text_block_saved?, :text_block + + def self.call(*_) + new(*_).call + end + + def initialize(params, text_block: TextBlock.new, + project: text_block.project) + @params = params + @text_block = text_block + @project = project + end + + + def call + @text_block.project = @project + @text_block.attributes = @params + + return Result.new text_block_saved: @text_block.save, + text_block: @text_block + end + end +end diff --git a/test/integration/text_blocks_admin_test.rb b/test/integration/text_blocks_admin_test.rb new file mode 100644 index 0000000..844e117 --- /dev/null +++ b/test/integration/text_blocks_admin_test.rb @@ -0,0 +1,49 @@ +require_relative '../test_helper' + +class TextBlocksAdminTest < Redmine::IntegrationTest + fixtures :users, :email_addresses, :user_preferences + + def setup + super + User.current = nil + end + + def test_textblocks_require_admin + get '/text_blocks' + assert_response :redirect + end + + def test_textblock_crud + log_user 'admin', 'admin' + + get '/text_blocks' + assert_response :success + + get '/text_blocks/new' + assert_response :success + + assert_difference 'TextBlock.count' do + post '/text_blocks', params: { text_block: { name: 'test', text: 'lorem ipsum'}} + end + assert_redirected_to '/text_blocks' + + follow_redirect! + + assert b = TextBlock.find_by_name('test') + assert_equal 'lorem ipsum', b.text + + get "/text_blocks/#{b.id}/edit" + assert_response :success + + patch "/text_blocks/#{b.id}", params: { text_block: { name: 'new' } } + b.reload + assert_equal 'lorem ipsum', b.text + assert_equal 'new', b.name + + assert_difference 'TextBlock.count', -1 do + delete "/text_blocks/#{b.id}" + end + assert_redirected_to '/text_blocks' + + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 0000000..8d9dc77 --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,2 @@ +require_relative '../../../test/test_helper' + diff --git a/test/unit/text_block_test.rb b/test/unit/text_block_test.rb new file mode 100644 index 0000000..a28a825 --- /dev/null +++ b/test/unit/text_block_test.rb @@ -0,0 +1,58 @@ +require_relative '../test_helper' + +class TextBlockTest < ActiveSupport::TestCase + fixtures :projects + + setup do + @project = Project.find 'ecookbook' + end + + test 'should require name' do + r = RedmineTextBlocks::SaveTextBlock.({}) + refute r.text_block_saved? + assert r.text_block.errors[:name] + end + + test 'should validate name uniqueness against global text block' do + assert_difference 'TextBlock.count' do + r = RedmineTextBlocks::SaveTextBlock.({name: 'test'}) + assert r.text_block_saved? + assert_equal 'test', r.text_block.name + end + + assert_no_difference 'TextBlock.count' do + r = RedmineTextBlocks::SaveTextBlock.({name: 'test'}) + refute r.text_block_saved? + assert r.text_block.errors[:name] + end + + assert_no_difference 'TextBlock.count' do + r = RedmineTextBlocks::SaveTextBlock.({name: 'test'}, project: @project) + refute r.text_block_saved? + assert r.text_block.errors[:name] + end + end + + test 'should validate name uniqueness against local text block' do + assert_difference 'TextBlock.count' do + r = RedmineTextBlocks::SaveTextBlock.({name: 'test'}, project: @project) + assert r.text_block_saved? + assert_equal 'test', r.text_block.name + end + + assert_no_difference 'TextBlock.count' do + r = RedmineTextBlocks::SaveTextBlock.({name: 'test'}) + refute r.text_block_saved? + assert r.text_block.errors[:name] + end + + project = Project.find 'onlinestore' + assert_difference 'TextBlock.count' do + r = RedmineTextBlocks::SaveTextBlock.({name: 'test'}, project: project) + assert r.text_block_saved? + assert_equal 'test', r.text_block.name + assert_equal project, r.text_block.project + end + end +end + From b33f66bfd9fa27932f2e1c81de66a86c133094b6 Mon Sep 17 00:00:00 2001 From: Jens Kraemer Date: Thu, 31 Aug 2017 15:36:55 +0800 Subject: [PATCH 2/5] per project text block admin --- app/controllers/text_blocks_controller.rb | 21 +++---- app/helpers/text_blocks_helper.rb | 2 + .../projects/settings/_text_blocks.html.erb | 6 ++ app/views/text_blocks/_list.html.erb | 17 +++++ app/views/text_blocks/_text_block.html.erb | 5 +- app/views/text_blocks/edit.html.erb | 4 +- app/views/text_blocks/index.html.erb | 18 +----- app/views/text_blocks/new.html.erb | 4 +- config/routes.rb | 2 +- init.rb | 3 +- .../project_settings_tabs.rb | 6 +- test/integration/text_blocks_project_test.rb | 62 +++++++++++++++++++ 12 files changed, 111 insertions(+), 39 deletions(-) create mode 100644 app/helpers/text_blocks_helper.rb create mode 100644 app/views/projects/settings/_text_blocks.html.erb create mode 100644 app/views/text_blocks/_list.html.erb create mode 100644 test/integration/text_blocks_project_test.rb diff --git a/app/controllers/text_blocks_controller.rb b/app/controllers/text_blocks_controller.rb index 03afab9..52dd3e5 100644 --- a/app/controllers/text_blocks_controller.rb +++ b/app/controllers/text_blocks_controller.rb @@ -1,10 +1,16 @@ class TextBlocksController < ApplicationController + layout ->{ @project ? 'base' : 'admin' } + + self.main_menu = false before_action :find_project_by_project_id before_action :require_admin, if: ->{ @project.nil? } before_action :authorize, if: ->{ @project.present? } + menu_item :settings, only: [:new, :create, :edit, :update, :destroy] + helper_method :index_path + def index @text_blocks = text_block_scope end @@ -21,11 +27,7 @@ def create r = RedmineTextBlocks::SaveTextBlock.(text_block_params, project: @project) if r.text_block_saved? - if params[:continue] - redirect_to new_path - else - redirect_to index_path - end + redirect_to params[:continue] ? new_path : index_path else @text_block = r.text_block render 'new' @@ -54,8 +56,9 @@ def destroy def new_path @project ? new_project_text_block_path(@project) : new_text_block_path end + def index_path - @project ? project_text_blocks_path(@project) : text_blocks_path + @project ? settings_project_path(@project, tab: 'text_blocks') : text_blocks_path end def text_block_params @@ -73,11 +76,7 @@ def find_project_by_project_id end def text_block_scope - text_blocks = TextBlock.order(name: :asc) - if @project - text_blocks = text_blocks.where(project_id: @project.id) - end - text_blocks + TextBlock.order(name: :asc).where(project_id: @project&.id) end end diff --git a/app/helpers/text_blocks_helper.rb b/app/helpers/text_blocks_helper.rb new file mode 100644 index 0000000..f11d99c --- /dev/null +++ b/app/helpers/text_blocks_helper.rb @@ -0,0 +1,2 @@ +module TextBlocksHelper +end diff --git a/app/views/projects/settings/_text_blocks.html.erb b/app/views/projects/settings/_text_blocks.html.erb new file mode 100644 index 0000000..7906cfa --- /dev/null +++ b/app/views/projects/settings/_text_blocks.html.erb @@ -0,0 +1,6 @@ +

+ <%= link_to l(:label_text_block_new), new_project_text_block_path(@project), class: 'icon icon-add' %> +

+ +<%= render partial: 'text_blocks/list', locals: { text_blocks: TextBlock.where(project_id: @project.id) } %> + diff --git a/app/views/text_blocks/_list.html.erb b/app/views/text_blocks/_list.html.erb new file mode 100644 index 0000000..b1db598 --- /dev/null +++ b/app/views/text_blocks/_list.html.erb @@ -0,0 +1,17 @@ +<% if text_blocks.any? %> + + + + + + + + + + <%= render collection: text_blocks, partial: 'text_blocks/text_block' %> + +
<%= l :label_text_block_name %><%= l :label_text_block_text %>
+<% else %> +

<%= l :label_no_data %>

+<% end %> + diff --git a/app/views/text_blocks/_text_block.html.erb b/app/views/text_blocks/_text_block.html.erb index fc48169..5e7a0f8 100644 --- a/app/views/text_blocks/_text_block.html.erb +++ b/app/views/text_blocks/_text_block.html.erb @@ -1,5 +1,6 @@ +<% path_for_text_block = text_block.project ? project_text_block_path(text_block.project, text_block) : text_block_path(text_block) %> - <%= link_to text_block.name, edit_text_block_path(text_block) %> + <%= link_to text_block.name, path_for_text_block %> <%= text_block.text %> - <%= delete_link text_block_path(text_block) %> + <%= delete_link path_for_text_block %> diff --git a/app/views/text_blocks/edit.html.erb b/app/views/text_blocks/edit.html.erb index 8563b13..6b6ef1c 100644 --- a/app/views/text_blocks/edit.html.erb +++ b/app/views/text_blocks/edit.html.erb @@ -1,6 +1,6 @@ -<%= title [l(:label_text_block_plural), text_blocks_path], @text_block.name %> +<%= title [l(:label_text_block_plural), index_path], @text_block.name %> -<%= labelled_form_for @text_block, url: text_block_path(@text_block), method: :patch do |f| %> +<%= labelled_form_for @text_block, url: (@project ? project_text_block_path(@project, @text_block) : text_block_path(@text_block)), method: :patch do |f| %> <%= render partial: 'form', locals: { f: f } %>

<%= submit_tag l :button_save %> diff --git a/app/views/text_blocks/index.html.erb b/app/views/text_blocks/index.html.erb index 93eb135..949cd11 100644 --- a/app/views/text_blocks/index.html.erb +++ b/app/views/text_blocks/index.html.erb @@ -4,23 +4,7 @@

<%= l :label_text_block_plural %>

-<% if @text_blocks.any? %> - - - - - - - - - - <%= render collection: @text_blocks, partial: 'text_block' %> - -
<%= l :label_text_block_name %><%= l :label_text_block_text %>
- -<% else %> -

<%= l :label_no_data %>

-<% end %> +<%= render partial: 'list', locals: { text_blocks: @text_blocks } %> <% html_title l :label_text_block_plural -%> diff --git a/app/views/text_blocks/new.html.erb b/app/views/text_blocks/new.html.erb index 703863f..ea77baa 100644 --- a/app/views/text_blocks/new.html.erb +++ b/app/views/text_blocks/new.html.erb @@ -1,6 +1,6 @@ -<%= title [l(:label_text_block_plural), text_blocks_path], l(:label_text_block_new) %> +<%= title [l(:label_text_block_plural), index_path], l(:label_text_block_new) %> -<%= labelled_form_for @text_block, url: text_blocks_path do |f| %> +<%= labelled_form_for @text_block, url: @project ? project_text_blocks_path(@project) : text_blocks_path do |f| %> <%= render partial: 'form', locals: { f: f } %>

<%= submit_tag l :button_create %> diff --git a/config/routes.rb b/config/routes.rb index 785fb4e..0189f97 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,6 @@ resources :text_blocks, only: %i(index new create edit update destroy) scope 'projects/:project_id' do - resources :text_blocks, only: %i(index new create edit update destroy), + resources :text_blocks, only: %i(new create edit update destroy), as: :project_text_blocks end diff --git a/init.rb b/init.rb index 92c81ea..91b4791 100644 --- a/init.rb +++ b/init.rb @@ -21,7 +21,8 @@ permission :view_text_blocks, {}, require: :member, read: :true permission :manage_text_blocks, { - text_blocks: %i(index new edit update create destroy) + text_blocks: %i( new edit update create destroy ), + projects: %i( manage_text_blocks ) }, require: :member end diff --git a/lib/redmine_text_blocks/project_settings_tabs.rb b/lib/redmine_text_blocks/project_settings_tabs.rb index 1612f0e..b09463f 100644 --- a/lib/redmine_text_blocks/project_settings_tabs.rb +++ b/lib/redmine_text_blocks/project_settings_tabs.rb @@ -1,6 +1,6 @@ module RedmineTextBlocks - # hook into the helper method that renders the project settings tabs + # hooks into the helper method that renders the project settings tabs module ProjectSettingsTabs def project_settings_tabs @@ -8,9 +8,9 @@ def project_settings_tabs if User.current.allowed_to?(:manage_text_blocks, @project) tabs << { name: 'text_blocks', - action: :text_blocks, + action: :manage_text_blocks, partial: 'projects/settings/text_blocks', - label: :label_text_blocks + label: :label_text_block_plural } end end diff --git a/test/integration/text_blocks_project_test.rb b/test/integration/text_blocks_project_test.rb new file mode 100644 index 0000000..3adbded --- /dev/null +++ b/test/integration/text_blocks_project_test.rb @@ -0,0 +1,62 @@ +require_relative '../test_helper' + +class TextBlocksProjectTest < Redmine::IntegrationTest + fixtures :users, :email_addresses, :user_preferences, + :roles, :projects, :members, :member_roles + + def setup + super + User.current = nil + @project = Project.find 'ecookbook' + EnabledModule.delete_all + EnabledModule.create! project: @project, name: 'text_blocks' + end + + def test_textblocks_require_permission + log_user 'jsmith', 'jsmith' + + get '/projects/ecookbook/settings' + assert_response :success + assert_select 'li a', text: 'Text blocks', count: 0 + post '/projects/ecookbook/text_blocks', params: { text_block: { name: 'new' }} + assert_response 403 + end + + def test_textblock_crud + Role.find(1).add_permission! :manage_text_blocks + + log_user 'jsmith', 'jsmith' + + get '/projects/ecookbook/settings' + assert_select 'li a', text: 'Text blocks' + + get '/projects/ecookbook/settings/text_blocks' + assert_response :success + + get '/projects/ecookbook/text_blocks/new' + assert_response :success + + assert_difference 'TextBlock.count' do + post '/projects/ecookbook/text_blocks', params: { text_block: { name: 'test', text: 'lorem ipsum'}} + end + assert_redirected_to '/projects/ecookbook/settings/text_blocks' + + follow_redirect! + + assert b = TextBlock.find_by_name('test') + assert_equal 'lorem ipsum', b.text + + get "/projects/ecookbook/text_blocks/#{b.id}/edit" + assert_response :success + + patch "/projects/ecookbook/text_blocks/#{b.id}", params: { text_block: { name: 'new' } } + b.reload + assert_equal 'lorem ipsum', b.text + assert_equal 'new', b.name + + assert_difference 'TextBlock.count', -1 do + delete "/projects/ecookbook/text_blocks/#{b.id}" + end + assert_redirected_to '/projects/ecookbook/settings/text_blocks' + end +end From b679a1c124a3b797a80ef88c7c108a466f4f2cbf Mon Sep 17 00:00:00 2001 From: Jens Kraemer Date: Thu, 31 Aug 2017 19:11:36 +0800 Subject: [PATCH 3/5] use text blocks in issues#edit --- app/helpers/text_blocks_helper.rb | 12 ++++++ .../_view_issues_edit_notes_bottom.html.erb | 4 ++ app/views/text_blocks/_text_block.html.erb | 5 +-- assets/javascripts/text_blocks.js | 40 +++++++++++++++++++ config/locales/en.yml | 1 + lib/redmine_text_blocks.rb | 4 +- lib/redmine_text_blocks/hooks.rb | 0 lib/redmine_text_blocks/view_hooks.rb | 8 ++++ 8 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 app/views/hooks/text_blocks/_view_issues_edit_notes_bottom.html.erb create mode 100644 assets/javascripts/text_blocks.js delete mode 100644 lib/redmine_text_blocks/hooks.rb create mode 100644 lib/redmine_text_blocks/view_hooks.rb diff --git a/app/helpers/text_blocks_helper.rb b/app/helpers/text_blocks_helper.rb index f11d99c..c104364 100644 --- a/app/helpers/text_blocks_helper.rb +++ b/app/helpers/text_blocks_helper.rb @@ -1,2 +1,14 @@ module TextBlocksHelper + def text_block_options + tags = [] + tags << content_tag(:option, value: '') do + t('label_select_text_block') + end + tags += TextBlock.where(project_id: [nil, @project.id]).to_a.map{|tb| + content_tag :option, value: tb.text do + tb.name + end + } + safe_join tags + end end 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 new file mode 100644 index 0000000..4a69baf --- /dev/null +++ b/app/views/hooks/text_blocks/_view_issues_edit_notes_bottom.html.erb @@ -0,0 +1,4 @@ +<% if User.current.allowed_to?(:view_text_blocks, @project) %> + <%= select_tag :text_block, text_block_options, 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;' %> + <%= javascript_include_tag 'text_blocks', plugin: 'redmine_text_blocks' %> +<% end %> diff --git a/app/views/text_blocks/_text_block.html.erb b/app/views/text_blocks/_text_block.html.erb index 5e7a0f8..8e3d15a 100644 --- a/app/views/text_blocks/_text_block.html.erb +++ b/app/views/text_blocks/_text_block.html.erb @@ -1,6 +1,5 @@ -<% path_for_text_block = text_block.project ? project_text_block_path(text_block.project, text_block) : text_block_path(text_block) %> - <%= link_to text_block.name, path_for_text_block %> + <%= link_to text_block.name, (text_block.project ? edit_project_text_block_path(text_block.project, text_block) : edit_text_block_path(text_block)) %> <%= text_block.text %> - <%= delete_link path_for_text_block %> + <%= delete_link(text_block.project ? project_text_block_path(text_block.project, text_block) : text_block_path(text_block)) %> diff --git a/assets/javascripts/text_blocks.js b/assets/javascripts/text_blocks.js new file mode 100644 index 0000000..57d0c1f --- /dev/null +++ b/assets/javascripts/text_blocks.js @@ -0,0 +1,40 @@ +var TextBlocks = { + init: function(){ + var select = $('#textblock-select'); + var toolbar_buttons = select.parent().find('.jstElements'); + if(select.parent() != toolbar_buttons) { + toolbar_buttons.find('button:last').after(select); + } + select.show(); + }, + + insert: function(e){ + var value = $(this).val(); + if(value == '') return; + + console.log(value); + var fieldId = $('#textblock-select').parent().next('div.jstEditor').find('textarea').attr('id'); + + var field = document.getElementById(fieldId); + var field_ = $(field); + + var caretPos = field.selectionStart; + var caretEnd = field.selectionEnd; + var textAreaTxt = field_.val(); + field_.val( + textAreaTxt.substring(0, caretPos) + + value + + textAreaTxt.substring(caretEnd) + ); + + field_.focus(); + field.selectionStart = caretPos + value.length + field.selectionEnd = caretPos + value.length + + $(this).val(''); + } + +} + +$(document).ready(TextBlocks.init); +$(document).on("change", "#textblock-select", TextBlocks.insert); diff --git a/config/locales/en.yml b/config/locales/en.yml index 1d8d966..219d6ec 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2,6 +2,7 @@ en: field_text_block_text: Text + label_select_text_block: "Select text block..." label_text_block_name: Name label_text_block_text: Text label_text_block_plural: Text blocks diff --git a/lib/redmine_text_blocks.rb b/lib/redmine_text_blocks.rb index 9a8853e..2a6f6c4 100644 --- a/lib/redmine_text_blocks.rb +++ b/lib/redmine_text_blocks.rb @@ -1,8 +1,8 @@ -require 'redmine_text_blocks/hooks' - +require 'redmine_text_blocks/view_hooks' module RedmineTextBlocks def self.setup ProjectsController.send :helper, RedmineTextBlocks::ProjectSettingsTabs + IssuesController.send :helper, TextBlocksHelper end end diff --git a/lib/redmine_text_blocks/hooks.rb b/lib/redmine_text_blocks/hooks.rb deleted file mode 100644 index e69de29..0000000 diff --git a/lib/redmine_text_blocks/view_hooks.rb b/lib/redmine_text_blocks/view_hooks.rb new file mode 100644 index 0000000..0a12645 --- /dev/null +++ b/lib/redmine_text_blocks/view_hooks.rb @@ -0,0 +1,8 @@ +module RedmineTextBlocks + class ViewHooks < Redmine::Hook::ViewListener + + render_on :view_issues_edit_notes_bottom, + partial: 'hooks/text_blocks/view_issues_edit_notes_bottom' + + end +end From 7ac0c9a33755f0356198ebb48a4d5d4fd4906acb Mon Sep 17 00:00:00 2001 From: Daniel Kastl Date: Sat, 2 Sep 2017 11:36:56 +0900 Subject: [PATCH 4/5] Adds admin menu icon, fixes #5 --- assets/images/text_signature.png | Bin 0 -> 524 bytes assets/stylesheets/text_blocks.css | 5 +++++ init.rb | 8 ++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 assets/images/text_signature.png create mode 100644 assets/stylesheets/text_blocks.css diff --git a/assets/images/text_signature.png b/assets/images/text_signature.png new file mode 100644 index 0000000000000000000000000000000000000000..c72fd8088279421973c26218b6d05424e53aabf4 GIT binary patch literal 524 zcmV+n0`vWeP)Vbo(qSIUSEps0Q zUAr;NZG^#_4#09w`|BfW=BSyYW{#RUYUZe!%aN8eSM+R$yO)9~xWneh`0_bwJ2z3R zvrY?I6E4E|7xT1sOU>o5-Te;Mw5*|^dcu`kX%q>;$_F=tdyg6kUl(HY>9E*FQ*c$& z3MlB3C=DXPUx+Ug^$VQ5Sw}K5f<(}Y&`cWm)^1Sn6&5VGT_T#eM}w!xAf8TiNJh=L z+NwumgW~8dELd=DCv~^pI^3L@@E91uoo@nRM;W|^4AqxbIW|Xz4i`!hT_{9glI)z0 z5%aN;^zQW$v0wqqva>X6a+Y1UwM!GBDKZu?k-~2uQ`kzx6c+#6H9iB|X|uV2x*{9^ O0000 {:class => 'icon'} end +class TextBlocksListener < Redmine::Hook::ViewListener + render_on :view_layouts_base_html_head, inline: <<-END + <%= stylesheet_link_tag 'text_blocks', :plugin => 'redmine_text_blocks' %> + END +end From 9967e38900cb729bb50fab3c65b5af735217bfae Mon Sep 17 00:00:00 2001 From: Daniel Kastl Date: Sat, 2 Sep 2017 11:44:27 +0900 Subject: [PATCH 5/5] Adds (not so good) Japanese translation --- config/locales/ja.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 config/locales/ja.yml diff --git a/config/locales/ja.yml b/config/locales/ja.yml new file mode 100644 index 0000000..75a9afd --- /dev/null +++ b/config/locales/ja.yml @@ -0,0 +1,13 @@ +en: + + field_text_block_text: テンプレート + + label_select_text_block: "テンプレートを選択..." + label_text_block_name: タイトル + label_text_block_text: テンプレート + label_text_block_plural: テンプレート + label_text_block_new: 新しいテンプレート + + model: + text_block: + name_uniqueness: その名前のテンプレートはすでに存在します。