Actualizado plugin Redmine Questions 1.0.0 light

This commit is contained in:
Manuel Cillero 2019-03-21 17:52:05 +01:00
parent 27e60f8ec1
commit 5d7889f1c9
140 changed files with 5342 additions and 1430 deletions

View file

@ -0,0 +1,107 @@
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
class QuestionsAnswersController < ApplicationController
unloadable
before_action :find_question, :only => [:new, :create]
before_action :find_answer, :only => [:update, :destroy, :edit, :show]
helper :questions
helper :watchers
helper :attachments
include QuestionsHelper
def new
@answer = QuestionsAnswer.new(:question => @question_item)
end
def edit
end
def update
@answer.safe_attributes = params[:answer]
@answer.save_attachments(params[:attachments])
if @answer.save
flash[:notice] = l(:label_answer_successful_update)
respond_to do |format|
format.html { redirect_to_question }
end
else
respond_to do |format|
format.html { render :edit}
end
end
end
def create
@answer = QuestionsAnswer.new
@answer.author = User.current
@answer.question = @question_item
@answer.safe_attributes = params[:answer]
@answer.save_attachments(params[:attachments])
if @answer.save
flash[:notice] = l(:label_answer_successful_added)
render_attachment_warning_if_needed(@answer)
end
redirect_to_question
end
def destroy
if @answer.destroy
flash[:notice] = l(:notice_successful_delete)
respond_to do |format|
format.html { redirect_to_question }
format.api { render_api_ok }
end
else
flash[:error] = l(:notice_unsuccessful_save)
end
end
def preview
if params[:id].present? && answer = Question.find_by_id(params[:id])
@previewed = answer
end
@text = (params[:answer] ? params[:answer][:content] : nil)
render :partial => 'common/preview'
end
private
def redirect_to_question
redirect_to question_path(@answer.question, :anchor => "question_item_#{@answer.id}")
end
def find_answer
@answer = QuestionsAnswer.find(params[:id])
@question_item = @answer.question
@project = @question_item.project
rescue ActiveRecord::RecordNotFound
render_404
end
def find_question
@question_item = Question.visible.find(params[:question_id]) unless params[:question_id].blank?
@project = @question_item.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View file

@ -0,0 +1,78 @@
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
class QuestionsCommentsController < ApplicationController
before_action :find_comment_source
helper :questions
def create
raise Unauthorized unless @comment_source.commentable?
@comment = Comment.new
@comment.safe_attributes = params[:comment]
@comment.author = User.current
if @comment_source.comments << @comment
@comment_source.touch
flash[:notice] = l(:label_comment_added) unless request.xhr?
end
respond_to do |format|
format.html { redirect_to_question }
format.js
end
end
def edit
@comment = @comment_source.comments.find(params[:id])
end
def update
@comment = @comment_source.comments.find(params[:id])
@comment.safe_attributes = params[:comment]
if @comment.save
flash[:notice] = l(:notice_successful_update)
redirect_to_question
else
render :action => 'edit'
end
end
def destroy
@comment_source.comments.find(params[:id]).destroy
redirect_to_question
end
private
def find_comment_source
comment_source_type = params[:source_type]
comment_source_id = params[:source_id]
klass = Object.const_get(comment_source_type.camelcase)
@comment_source = klass.find(comment_source_id)
rescue ActiveRecord::RecordNotFound
render_404
end
def redirect_to_question
question = @comment_source.is_a?(QuestionsAnswer) ? @comment_source.question : @comment_source
redirect_to question_path(question, :anchor => @comment.blank? ? "#{@comment_source.class.name.underscore}_#{@comment_source.id}" : "comment_#{@comment.id}")
end
end

View file

@ -1,76 +1,146 @@
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
class QuestionsController < ApplicationController
unloadable
before_filter :find_optional_project, :only => [:autocomplete_for_topic, :topics]
before_filter :find_optional_board, :only => [:autocomplete_for_topic, :topics]
before_filter :find_topic, :authorize, :only => :vote
before_filter :find_topics, :only => [:topics, :autocomplete_for_topic]
before_action :find_question, :only => [:edit, :show, :update, :destroy]
before_action :find_optional_project, :only => [:index, :update_form, :new, :create, :autocomplete_for_subject]
before_action :find_section, :only => [:new, :create, :update, :edit]
before_action :find_questions, :only => [:autocomplete_for_subject, :index] #:autocomplete_for_subject
helper :questions
if Redmine::VERSION.to_s > '2.1'
helper :boards
end
helper :watchers
helper :attachments
include QuestionsHelper
def index
@boards = Board.visible.includes(:last_message => :author).includes(:messages).order(:project_id)
# show the board if there is only one
if @boards.size == 1
@board = @boards.first
redirect_to project_board_url(@board, :project_id => @board.project)
@question_item = Question.new
end
def new
@question_item = Question.new
@question_item.section ||= @section
end
def show
@answers = @question_item.answers.by_accepted.by_votes.by_date
if @answers
@limit = Setting.issues_export_limit.to_i
@answer_count = @answers.count
@answer_pages = Paginator.new @answer_count, @limit, (params[:page] || 1)
@offset ||= @answer_pages.offset
end
@answer = QuestionsAnswer.new
@answer.question = @question_item
@question_item.view request.remote_addr, User.current
end
def update
@question_item.safe_attributes = params[:question]
@question_item.save_attachments(params[:attachments])
if @question_item.save
flash[:notice] = l(:label_question_successful_update)
respond_to do |format|
format.html {redirect_to :action => :show, :id => @question_item}
end
else
render "boards/index"
respond_to do |format|
format.html { render :edit}
end
end
end
def topics
def update_form
@question_item = Question.new
@question_item.safe_attributes = params[:question]
end
def vote
User.current.voted_for?(@topic) ? @topic.dislike(User.current.becomes(Principal)) : @topic.like(User.current.becomes(Principal))
def create
@question_item = Question.new
@question_item.section = @section
@question_item.safe_attributes = params[:question]
@question_item.author = User.current
@question_item.save_attachments(params[:attachments])
respond_to do |format|
format.html { redirect_to_referer_or {render :text => (watching ? 'Vote added.' : 'Vote removed.'), :layout => true}}
if @question_item.save
format.html { redirect_to :action => :show, :id => @question_item}
else
format.html { render :action => 'new' }
end
end
end
def autocomplete_for_topic
def autocomplete_for_subject
render :layout => false
end
def convert_issue
issue = Issue.visible.find(params[:issue_id])
board = Board.visible.find(params[:board_id])
message = Message.new
message.author = issue.author
message.created_on = issue.created_on
message.board = board
message.subject = issue.subject
message.content = issue.description.blank? ? issue.subject : issue.description
message.watchers = issue.watchers
message.add_watcher(issue.author)
message.attachments = issue.attachments
issue.journals.select{|j| !j.notes.blank?}.each do |journal|
reply = Message.new
reply.author = journal.user
reply.created_on = journal.created_on
reply.subject = "Re: #{message.subject}"
reply.content = journal.notes
reply.board = board
message.children << reply
end
if message.save
issue.destroy if params[:destroy]
redirect_to board_message_path(board, message)
else
redirect_back_or_default({:controller => 'issues', :action => 'show', :id => issue})
end
# def convert_issue_to_question
# issue = Issue.visible.find(params[:issue_id])
# question = Question.from_issue(issue)
# if question.save
# issue.destroy if params[:destroy]
# redirect_to _question_path(question)
# else
# redirect_back_or_default({:controller => 'issues', :action => 'show', :id => issue})
# end
# end
# def convert_to_issue
# issue = @question_item.to_issue
# if issue.save
# redirect_to issue_path(issue)
# else
# redirect_back_or_default question_path(@question_item)
# end
# end
def destroy
back_id = @question_item.section
if @question_item.destroy
flash[:notice] = l(:notice_successful_delete)
else
flash[:error] = l(:notice_unsuccessful_save)
end
respond_to do |format|
format.html { redirect_back_or_default questions_path(:section_id => back_id) }
format.api { render_api_ok }
end
end
private
def preview
if params[:id].present? && query = Question.find_by_id(params[:question_id])
@previewed = query
end
@text = (params[:question] ? params[:question][:content] : nil)
render :partial => 'common/preview'
end
private
def find_topics
def find_questions
seach = params[:q] || params[:topic_search]
@section = QuestionsSection.find(params[:section_id]) if params[:section_id]
scope = Question.visible
scope = scope.where(:section_id => @section) if @section
columns = ["subject", "content"]
tokens = seach.to_s.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect{|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')}.uniq.select {|w| w.length > 1 }
@ -79,39 +149,49 @@ private
sql = (['(' + token_clauses.join(' OR ') + ')'] * tokens.size).join(' AND ')
find_options = [sql, * (tokens.collect {|w| "%#{w.downcase}%"} * token_clauses.size).sort]
scope = Message.joins(:board).where({})
scope = scope.where("#{Message.table_name}.parent_id IS NULL")
scope = scope.where(["#{Board.table_name}.project_id = ?", @project.id]) if @project
scope = scope.where(["#{Message.table_name}.board_id = ?", @board.id]) if @board
scope = scope.in_project(@project)
scope = scope.where(find_options) unless tokens.blank?
scope = scope.visible.includes(:board).order("#{Message.table_name}.updated_on DESC")
@sort_order = params[:sort_order]
case @sort_order
when 'popular'
scope = scope.by_views.by_update
when 'newest'
scope = scope.by_date
when 'active'
scope = scope.by_update
when 'unanswered'
scope = scope.questions.where(:answers_count => 0)
else
scope = scope.by_votes.by_views.by_update
end
@topic_count = scope.count
@limit = per_page_option
@topic_pages = Paginator.new(self, @topic_count, @limit, params[:page])
@offset = @topic_pages.current.offset
@offset = params[:page].to_i*@limit
scope = scope.limit(@limit).offset(@offset)
scope = scope.tagged_with(params[:tag]) unless params[:tag].blank?
@topics = scope
@topic_count = scope.count
@topic_pages = Paginator.new @topic_count, @limit, params[:page]
@question_items = scope
end
def find_topic
@topic = Message.visible.find(params[:id]) unless params[:id].blank?
@board = @topic.board
@project = @board.project
def find_section
@section = QuestionsSection.find_by_id(params[:section_id] || (params[:question] && params[:question][:section_id]))
@section ||= @project.questions_sections.first
rescue ActiveRecord::RecordNotFound
render_404
end
def find_optional_board
@board = Board.visible.find(params[:board_id]) unless params[:board_id].blank?
@project = @board.project if @board
allowed = User.current.allowed_to?({:controller => params[:controller], :action => params[:action]}, @project, :global => true)
allowed ? true : deny_access
def find_question
if Redmine::VERSION.to_s =~ /^2.6/
@question_item = Question.visible.find(params[:id], readonly: false)
else
@question_item = Question.visible.find(params[:id])
end
return deny_access unless @question_item.visible?
@project = @question_item.project
rescue ActiveRecord::RecordNotFound
render_404
end
end

View file

@ -0,0 +1,106 @@
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
class QuestionsSectionsController < ApplicationController
menu_item :questions
before_action :find_section, :only => [:edit, :update, :destroy]
before_action :find_optional_project, :only => [:index, :new, :create]
helper :questions
def new
@section = @project.questions_sections.build
respond_to do |format|
format.html
format.js
end
end
def create
@section = @project.nil? ? QuestionsSection.new : @project.questions_sections.build
@section.safe_attributes = params[:questions_section]
if @section.save
respond_to do |format|
format.html do
flash[:notice] = l(:notice_successful_create)
redirect_to_settings_in_projects
end
format.js
end
else
respond_to do |format|
format.html { render :action => 'new' }
format.js
end
end
end
def edit
end
def update
@section.safe_attributes = params[:questions_section]
if @section.save
respond_to do |format|
format.html do
flash[:notice] = l(:notice_successful_update)
redirect_to_settings_in_projects
end
format.js { head 200 }
end
else
respond_to do |format|
format.html { render :action => 'edit' }
format.js { head 422 }
end
end
end
def destroy
@section.destroy
respond_to do |format|
format.html { redirect_to_settings_in_projects }
end
end
def index
ApplicationController.menu_item :questions
@question_item = Question.new
@sections = QuestionsSection.visible.order(:project_id).sorted.for_project(@project)
redirect_to project_questions_path(:section_id => @sections.last, :project_id => @sections.last.project) if @sections.size == 1
@sections = @sections.with_questions_count
end
private
def find_section
@section = QuestionsSection.find(params[:id])
@project = @section.project
rescue ActiveRecord::RecordNotFound
render_404
end
def redirect_to_settings_in_projects
redirect_back_or_default( @project ? settings_project_path(@project, :tab => 'questions') : plugin_settings_path(:id => "redmine_questions"))
end
end

View file

@ -0,0 +1,82 @@
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
class QuestionsStatusesController < ApplicationController
unloadable
layout 'admin'
before_action :require_admin, :except => :index
before_action :require_admin_or_api_request, :only => :index
accept_api_auth :index
def index
respond_to do |format|
format.api {
@questions_statuses = QuestionsStatus.sorted
}
end
end
def new
@questions_status = QuestionsStatus.new
end
def create
@questions_status = QuestionsStatus.new(params[:questions_status])
if request.post? && @questions_status.save
flash[:notice] = l(:notice_successful_create)
redirect_to :action => "plugin", :id => "redmine_questions", :controller => "settings", :tab => 'questions_statuses'
else
render :action => 'new'
end
end
def edit
@questions_status = QuestionsStatus.find(params[:id])
end
def update
@questions_status = QuestionsStatus.find(params[:id])
if @questions_status.update_attributes(params[:questions_status])
respond_to do |format|
format.html {
flash[:notice] = l(:notice_successful_update)
redirect_to :action => 'plugin', :id => 'redmine_questions', :controller => 'settings', :tab => 'questions_statuses'
}
format.js { head 200 }
end
else
respond_to do |format|
format.html { render :action => 'edit' }
format.js { head 422 }
end
end
end
def destroy
QuestionsStatus.find(params[:id]).destroy
redirect_to :action =>"plugin", :id => "redmine_questions", :controller => "settings", :tab => 'questions_statuses'
rescue
flash[:error] = l(:error_products_unable_delete_questions_status)
redirect_to :action =>"plugin", :id => "redmine_questions", :controller => "settings", :tab => 'questions_statuses'
end
end

View file

@ -1,15 +1,51 @@
# encoding: utf-8
#
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
module QuestionsHelper
def vote_tag(object, user, options={})
content_tag("span", vote_link(object, user))
def question_status_tag(status)
return '' unless status
content_tag(:span, status.name, :class => 'question-status-tag tag-label-color', :style => "background-color: #{status.color}")
end
def vote_link(object, user)
return '' unless user && user.logged? && user.respond_to?('voted_for?')
voted = user.voted_for?(object)
url = {:controller => 'questions', :action => 'vote', :id => object}
link_to((voted ? l(:button_questions_unvote) : l(:button_questions_vote)), url,
:class => (voted ? 'icon icon-vote' : 'icon icon-unvote'))
def allow_voting?(votable, user = User.current)
(votable.author == user && QuestionsSettings.vote_own? || votable.author != user) &&
user.allowed_to?(:vote_questions, votable.project)
end
end
def question_breadcrumb(item)
links = []
links << link_to(l(:label_questions), { :controller => 'questions_sections', :action => 'index', :project_id => nil})
links << link_to(item.project.name, { :controller => 'questions_sections', :action => 'index', :project_id => item.project }) if item && item.project
links << link_to(item.section.name, { :controller => 'questions', :action => 'index', :project_id => item.project, :section_id => item.section }) if item && item.is_a?(Question) && item.section.present?
breadcrumb links
end
def global_modificator
return {:global => true} if !@project
{}
end
def path_to_sections
return project_questions_sections_path if @project
questions_sections_path
end
end

View file

@ -0,0 +1,290 @@
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
class Question < ActiveRecord::Base
unloadable
include Redmine::SafeAttributes
extend ApplicationHelper
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
belongs_to :section, :class_name => 'QuestionsSection', :foreign_key => 'section_id'
belongs_to :status, :class_name => 'QuestionsStatus', :foreign_key => 'status_id'
delegate :section_type, :to => :section, :allow_nil => true
has_many :answers, :class_name => 'QuestionsAnswer', :dependent => :destroy
if ActiveRecord::VERSION::MAJOR >= 4
has_many :comments, lambda { order('created_on') }, :as => :commented, :dependent => :delete_all
else
has_many :comments, :as => :commented, :dependent => :delete_all, :order => "created_on"
end
rcrm_acts_as_viewed
acts_as_attachable_questions
acts_as_watchable
acts_as_event :datetime => :created_on,
:url => Proc.new {|o| {:controller => 'questions', :action => 'show', :id => o }},
:type => Proc.new {|o| 'icon ' + (o.is_solution? ? 'icon-solution': 'icon-question')},
:description => :content,
:title => Proc.new {|o| o.subject }
if ActiveRecord::VERSION::MAJOR >= 4
acts_as_activity_provider :type => 'questions',
:permission => :view_questions,
:author_key => :author_id,
:timestamp => "#{table_name}.created_on",
:scope => joins({:section => :project}, :author)
acts_as_searchable :columns => ["#{table_name}.subject",
"#{table_name}.content",
"#{QuestionsAnswer.table_name}.content"],
:scope => joins({:section => :project}, :answers),
:project_key => "#{QuestionsSection.table_name}.project_id"
else
acts_as_activity_provider :type => 'questions',
:permission => :view_questions,
:author_key => :author_id,
:timestamp => "#{table_name}.created_on",
:find_options => { :include => [{:section => :project}, :author] }
acts_as_searchable :columns => ["#{table_name}.subject",
"#{table_name}.content",
"#{QuestionsAnswer.table_name}.content"],
:include => [{:section => :project}, :answers],
:project_key => "#{QuestionsSection.table_name}.project_id"
end
scope :solutions, lambda { joins(:section).where(:questions_sections => {:section_type => QuestionsSection::SECTION_TYPE_SOLUTIONS}) }
scope :questions, lambda { joins(:section).where(:questions_sections => {:section_type => QuestionsSection::SECTION_TYPE_QUESTIONS}) }
scope :by_votes, lambda { order("#{Question.table_name}.cached_weighted_score DESC") }
scope :by_date, lambda { order("#{Question.table_name}.created_on DESC") }
scope :by_update, lambda { order("#{Question.table_name}.updated_on DESC") }
scope :by_views, lambda { order("#{Question.table_name}.views DESC") }
scope :positive, lambda { where("#{Question.table_name}.cached_weighted_score > 0") }
scope :featured, lambda {|*args| where(:featured => true) }
scope :in_section, lambda { |section|
where(:section_id => section) if section.present?
}
scope :in_project, lambda { |project|
joins(:section => :project).where("#{QuestionsSection.table_name}.project_id = ?", project) if project.present?
}
scope :visible, lambda { |*args|
joins(:section => :project)
.where(Project.allowed_to_condition(args.shift || User.current, :view_questions, *args))
}
validates_presence_of :author, :content, :subject, :section
after_create :add_author_as_watcher
after_create :send_notification
safe_attributes 'author',
'subject',
'content',
'tag_list',
'section_id',
'status_id'
safe_attributes 'status_id',
:if => lambda {|question, user| question.is_idea?}
def self.visible_condition(user)
user.reload if user
global_questions_allowed = user.allowed_to?(:view_questions, nil)
projects_allowed_to_view_questions = Project.where(Project.allowed_to_condition(user, :view_questions)).pluck(:id)
allowed_to_view_condition = global_questions_allowed ? "(#{table_name}.project_id IS NULL)" : '(0=1)'
allowed_to_view_condition += projects_allowed_to_view_questions.empty? ? ' OR (0=1) ' : " OR (#{table_name}.project_id IN (#{projects_allowed_to_view_questions.join(',')}))"
user.admin? ? '(1=1)' : allowed_to_view_condition
end
def self.related(question, limit)
tokens = question.subject.strip.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).
collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '').gsub(%r{('|"|`)}, '')}.select{|m| m.size > 3} || ""
related_questions = where(tokens.map{ |t| "LOWER(subject) LIKE LOWER('%#{t}%')" }.join(' OR '))
related_questions = related_questions.in_project(question.project)
related_questions = related_questions.where("#{Question.table_name}.id != ?", question.id)
related_questions.limit(limit).to_a.compact
end
def commentable?(user = User.current)
return false if locked?
user.allowed_to?(:comment_question, project)
end
def visible?(user=User.current)
user.allowed_to?(:view_questions, project)
end
def to_param
"#{id}-#{ActiveSupport::Inflector.transliterate(subject || " ").parameterize}"
end
def section_name
section.try(:name)
end
def project
section.project
end
def allow_voting?
false
end
def allow_liking?
section.allow_liking?
end
def allow_answering?
!locked? && section.allow_answering?
end
def last_reply
answers.order('created_on DESC').last
end
def last_comment
Comment.where(:commented_type => self.class.name, :commented_id => [id] + answer_ids).order('created_on DESC').last
end
def replies_count
answers.count
end
def editable_by?(user)
(author == user && user.allowed_to?(:edit_own_questions, project)) ||
user.allowed_to?(:edit_questions, project)
end
def destroyable_by?(user)
user.allowed_to?(:delete_questions, project)
end
def votable_by?(user)
user.allowed_to?(:vote_questions, project)
end
def convertable_by?(user)
return false if project.blank?
user.allowed_to?(:convert_questions, project)
end
def answered?
answers.where(:accepted => true).any?
end
def is_question?
section && section.is_questions?
end
def is_solution?
section && section.is_solutions?
end
def is_idea?
section && section.is_ideas?
end
# def to_issue
# issue = Issue.new
# issue.author = self.author
# issue.created_on = self.created_on
# issue.subject = self.subject
# issue.description = self.content
# issue.watchers = self.watchers
# issue.attachments = self.attachments
# issue.project = self.project
# issue.tracker = self.project.trackers.first
# issue.status = IssueStatus.first
# self.answers.each do |ans|
# journal = Journal.new(:notes => ans.content, :user => ans.author)
# issue.journals << journal
# end
# issue
# end
# def self.from_issue(issue)
# question = Question.new
# question.author = issue.author
# question.created_on = issue.created_on
# question.subject = issue.subject
# question.content = issue.description.blank? ? issue.subject : issue.description
# question.watchers = issue.watchers
# question.attachments = issue.attachments
# question.project = issue.project
# question.section = issue.project.questions_sections.first
# issue.journals.select{|j| j.notes.present?}.each do |journal|
# reply = Question.new
# reply.author = journal.user
# reply.created_on = journal.created_on
# reply.content = journal.notes
# reply.project = issue.project
# reply.question = question
# question.answers << reply
# end
# question
# end
def notified_users
project.notified_users.select { |user| visible?(user) }.collect(&:mail)
end
def self.to_text(input)
textile_glyphs = {
'&#8217;' => "'",
'&#8216' => "'",
'&lt;' => '<',
'&gt;' => '>',
'&#8221;' => "'",
'&#8220;' => '"',
'&#8230;' => '...',
'\1&#8212;' => '--',
' &rarr; ' => '->',
'&para;' => ' ',
' &#8211; ' => '-',
'&#215;' => '-',
'&#8482;' => '(TM)',
'&#174;' => '(R)',
'&#169;' => '(C)',
'&amp;' => '&'
}.freeze
html_regexp = /<(?:[^>"']+|"(?:\\.|[^\\"]+)*"|'(?:\\.|[^\\']+)*')*>/xm
input.dup.gsub(html_regexp, '').tap do |h|
textile_glyphs.each do |entity, char|
h.gsub!(entity, char)
end
end
end
private
def add_author_as_watcher
Watcher.create(:watchable => self, :user => author)
end
def send_notification
Mailer.question_question_added(self).deliver if Setting.notified_events.include?('question_added')
end
end

View file

@ -0,0 +1,126 @@
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
class QuestionsAnswer < ActiveRecord::Base
unloadable
include Redmine::SafeAttributes
extend ApplicationHelper
belongs_to :author, :class_name => 'User', :foreign_key => 'author_id'
belongs_to :question, :counter_cache => 'answers_count', :touch => true
attr_protected :id if ActiveRecord::VERSION::MAJOR <= 4
acts_as_attachable_questions
acts_as_event :datetime => :created_on,
:url => Proc.new {|o| {:controller => 'questions', :action => 'show', :id => o.question, :anchor => "questions_answer_#{o.id}" }},
:group => :question,
:type => Proc.new {|o| 'icon icon-reply'},
:description => :content,
:title => Proc.new {|o| o.question.subject }
if ActiveRecord::VERSION::MAJOR >= 4
acts_as_activity_provider :type => 'questions',
:permission => :view_questions,
:author_key => :author_id,
:timestamp => "#{table_name}.created_on",
:scope => joins({ :question => { :section => :project } }, :author)
else
acts_as_activity_provider :type => 'questions',
:permission => :view_questions,
:author_key => :author_id,
:timestamp => "#{table_name}.created_on",
:find_options => { :joins => [{ :question => { :section => :project } }, :author] }
end
scope :visible, lambda {|*args| where(Question.visible_condition(args.shift || User.current)) }
scope :by_votes, lambda { order("#{table_name}.cached_weighted_score DESC") }
scope :by_accepted, lambda { order("#{table_name}.accepted DESC") }
scope :by_date, lambda { order("#{table_name}.created_on DESC") }
scope :featured, lambda {|*args| where(:featured => true) }
validates_presence_of :question, :author, :content
validate :cannot_answer_to_locked_question, :on => :create
after_create :add_author_as_watcher
after_create :send_notification
safe_attributes 'author',
'content'
def commentable?(user = User.current)
return false if question.locked?
user.allowed_to?(:comment_question, project)
end
def cannot_answer_to_locked_question
# Can not reply to a locked topic
errors.add :base, 'Question is locked' if question && question.locked?
end
def section_name
question.try(:section).try(:name)
end
def project
question.project if question
end
def allow_voting?
question && question.section && question.section.allow_voting?
end
def last_comment
Comment.where(:commented_type => self.class.name, :commented_id => [id] + answer_ids).order('created_on DESC').last
end
def replies_count
answers.count
end
def editable_by?(user)
user.allowed_to?(:edit_questions, project)
end
def destroyable_by?(user)
user.allowed_to?(:delete_answers, project)
end
def votable_by?(user)
user.allowed_to?(:vote_questions, project)
end
private
def check_accepted
question.answers.update_all(:accepted => false) if question &&
accepted? &&
accepted_changed?
end
# </PRO>
def add_author_as_watcher
Watcher.create(:watchable => question, :user => author)
end
def send_notification
Mailer.question_answer_added(self).deliver if Setting.notified_events.include?('question_answer_added')
end
end

View file

@ -0,0 +1,92 @@
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
class QuestionsSection < ActiveRecord::Base
unloadable
include Redmine::SafeAttributes
belongs_to :project
has_many :questions, :foreign_key => "section_id", :dependent => :destroy
attr_protected :id if ActiveRecord::VERSION::MAJOR <= 4
safe_attributes 'name', 'project', 'position', 'description', 'section_type'
scope :with_questions_count, lambda { select("#{QuestionsSection.table_name}.*, count(#{QuestionsSection.table_name}.id) as questions_count").joins(:questions).order("project_id ASC").group("#{QuestionsSection.table_name}.id, #{QuestionsSection.table_name}.name, #{QuestionsSection.table_name}.project_id, #{QuestionsSection.table_name}.section_type") }
scope :for_project, lambda { |project| where(:project_id => project) unless project.blank? }
scope :visible, lambda {|*args|
joins(:project).
where(Project.allowed_to_condition(args.shift || User.current, :view_questions, *args))
}
scope :sorted, lambda { order(:position) }
rcrm_acts_as_list :scope => 'project_id = #{project_id}'
acts_as_watchable
SECTION_TYPE_QUESTIONS = 'questions'
validates_presence_of :section_type, :project_id, :name
validates_uniqueness_of :name, :scope => :project_id
def initialize(attributes=nil, *args)
super
if new_record?
# set default values for new records only
self.section_type ||= SECTION_TYPE_QUESTIONS
end
end
def to_param
"#{id}-#{ActiveSupport::Inflector.transliterate(name).parameterize}"
end
def is_questions?
true
end
def is_solutions?
false
end
def is_ideas?
false
end
def allow_voting?
false
end
def allow_liking?
false
end
def allow_answering?
is_questions?
end
def self.types_list
end
def l_type
I18n.t("label_questions_section_type_#{section_type}") if section_type
end
def to_s
name
end
end

View file

@ -0,0 +1,47 @@
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
class QuestionsSettings
unloadable
IDEA_COLORS = {
:green => 'green',
:blue => 'blue',
:turquoise => 'turquoise',
:light_green => 'lightgreen',
:yellow => 'yellow',
:orange => 'orange',
:red => 'red',
:purple => 'purple',
:gray => 'gray'
}
class << self
def vote_own?
false
end
def show_popular?
false
end
end
end

View file

@ -0,0 +1,38 @@
# This file is a part of Redmine Q&A (redmine_questions) plugin,
# Q&A plugin for Redmine
#
# Copyright (C) 2011-2018 RedmineUP
# http://www.redmineup.com/
#
# redmine_questions is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# redmine_questions is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with redmine_questions. If not, see <http://www.gnu.org/licenses/>.
class QuestionsStatus < ActiveRecord::Base
unloadable
include Redmine::SafeAttributes
belongs_to :question
attr_protected :id if ActiveRecord::VERSION::MAJOR <= 4
safe_attributes 'name', 'is_closed', 'position', 'color'
validates :name, :presence => true, :uniqueness => true
scope :sorted, lambda { order(:position) }
rcrm_acts_as_list
def to_s
name
end
end

View file

@ -0,0 +1,6 @@
<%= raw @questions_tags.map { |question_tag| {
'id' => @names_only ? question_tag.name : question_tag.id,
'text' => question_tag.name
}
}.to_json
%>

View file

@ -1 +0,0 @@
<%= render :partial => "questions/forums" %>

View file

@ -1,64 +0,0 @@
<%= board_breadcrumb(@board) %>
<div class="board details">
<div class="contextual">
<%= link_to_if_authorized l(:label_message_new),
{:controller => 'messages', :action => 'new', :board_id => @board},
:class => 'icon icon-add',
:onclick => 'showAndScrollTo("add-message", "message_subject"); return false;' %>
<%= content_tag('span', watcher_link(@board, User.current), :id => 'watcher') %>
</div>
<div id="add-message" style="display:none;">
<% if authorize_for('messages', 'new') %>
<h2><%= link_to h(@board.name), :controller => 'boards', :action => 'show', :project_id => @project, :id => @board %> &#187; <%= l(:label_message_new) %></h2>
<%= form_for @message, :url => {:controller => 'messages', :action => 'new', :board_id => @board}, :html => {:multipart => true, :id => 'message-form'} do |f| %>
<%= render :partial => 'messages/form', :locals => {:f => f} %>
<p><%= submit_tag l(:button_create) %>
<%= preview_link({:controller => 'messages', :action => 'preview', :board_id => @board}, 'message-form') %> |
<%= link_to l(:button_cancel), "#", :onclick => '$("#add-message").hide(); return false;' %></p>
<% end %>
<div id="preview" class="wiki"></div>
<% end %>
</div>
<h2><%=h @board.name %></h2>
<p class="subtitle"><%=h @board.description %></p>
<div class="filters">
<%= form_tag({:controller => "questions", :action => "topics" }, :method => :get, :id => "query_form") do %>
<%= hidden_field_tag('project_id', @project.to_param) if @project %>
<%= hidden_field_tag('board_id', @board.to_param) if @board %>
<% no_filters = true %>
<%= text_field_tag(:topic_search, params[:topic_search], :autocomplete => "off", :class => "questions-search", :placeholder => l(:label_questions_search) ) %>
<%= javascript_tag "observeSearchfield('topic_search', 'topics_list', '#{ escape_javascript(autocomplete_for_topic_questions_path(:project_id => @project, :board_id => @board)) }')" %>
<% end %>
</div>
</div>
<div id="topics_list" >
<%= render :partial => "questions/topic_list" %>
</div>
<% other_formats_links do |f| %>
<%= f.link_to 'Atom', :url => {:key => User.current.rss_key} %>
<% end %>
<% content_for :sidebar do %>
<%= render :partial => "questions/notice" %>
<%= render :partial => "questions/tag_cloud" %>
<%= render :partial => "questions/voted_topics" %>
<% end %>
<% html_title @board.name %>
<% content_for :header_tags do %>
<%= auto_discovery_link_tag(:atom, {:format => 'atom', :key => User.current.rss_key}, :title => "#{@project}: #{@board}") %>
<%= javascript_include_tag :questions, :plugin => 'redmine_questions' %>
<% end %>

View file

@ -0,0 +1,5 @@
<h1><%= @question.section_name %>: <%= link_to(@question.subject, @question_url) %></h1>
<p><%= l(:text_user_wrote, :value => h(@answer.author)) %></p>
<%= textilizable @answer, :content, :only_path => false %>

View file

@ -0,0 +1,6 @@
<%= @question.section_name %>: <%= @question.subject %>
<%= @question_url %>
<%= l(:text_user_wrote, :value => @answer.author) %>
<%= @answer.content %>

View file

@ -0,0 +1,5 @@
<h1><%= @question.section_name %>: <%= link_to(@question.subject, @question_url) %></h1>
<p><%= l(:text_user_wrote, :value => h(@comment.author)) %></p>
<%= textilizable @comment, :comments, :only_path => false %>

View file

@ -0,0 +1,6 @@
<%= @question.section_name %>: <%= @question.subject %>
<%= @question_url %>
<%= l(:text_user_wrote, :value => @comment.author) %>
<%= @comment.comments %>

View file

@ -0,0 +1,5 @@
<h1><%= @question.section_name %>: <%= link_to(@question.subject, @question_url) %></h1>
<p><%= l(:text_user_wrote, :value => h(@question.author)) %></p>
<%= textilizable @question, :content, :only_path => false %>

View file

@ -0,0 +1,6 @@
<%= @question.section_name %>: <%= @question.subject %>
<%= @question_url %>
<%= l(:text_user_wrote, :value => @question.author) %>
<%= @question.content %>

View file

@ -1,39 +0,0 @@
<%= error_messages_for 'message' %>
<% replying ||= false %>
<div class="box">
<!--[form:message]-->
<p style=<%= "display:none;" if replying %> ><label for="message_subject"><%= l(:field_subject) %></label><br />
<%= f.text_field :subject, :style => "width: 80%;", :id => "message_subject" %>
<% unless replying %>
<% if @message.safe_attribute? 'sticky' %>
<%= f.check_box :sticky %> <%= label_tag 'message_sticky', l(:label_board_sticky) %>
<% end %>
<% if @message.safe_attribute? 'locked' %>
<%= f.check_box :locked %> <%= label_tag 'message_locked', l(:label_board_locked) %>
<% end %>
<% end %>
</p>
<% if !replying && !@message.new_record? && @message.safe_attribute?('board_id') %>
<p><label><%= l(:label_board) %></label><br />
<%= f.select :board_id, boards_options_for_select(@message.project.boards) %></p>
<% end %>
<p>
<%= label_tag "message_content", l(:description_message_content), :class => "hidden-for-sighted" %>
<%= f.text_area :content, :cols => 80, :rows => 15, :class => 'wiki-edit', :id => 'message_content' %></p>
<%= wikitoolbar_for 'message_content' %>
<!--[eoform:message]-->
<% if !replying && @message.safe_attribute?('tag_list') %>
<p>
<label for="message_tag_list"><%= l(:field_questions_tags) %></label><br />
<%= render :partial => 'questions/form_tags' %>
</p>
<% end %>
<p><%= l(:label_attachment_plural) %><br />
<%= render :partial => 'attachments/form', :locals => {:container => @message} %></p>
</div>

View file

@ -1,18 +0,0 @@
<%= board_breadcrumb(@message) %>
<h2><%= avatar(@topic.author, :size => "24") %><%=h @topic.subject %></h2>
<%= form_for @message, {
:as => :message,
:url => {:action => 'edit'},
:html => {:multipart => true,
:id => 'message-form',
:method => :post}
} do |f| %>
<%= render :partial => 'form',
:locals => {:f => f, :replying => !@message.parent.nil?} %>
<%= submit_tag l(:button_save) %>
<%= preview_link({:controller => 'messages', :action => 'preview', :board_id => @board, :id => @message}, 'message-form') %>
<% end %>
<div id="preview" class="wiki"></div>

View file

@ -1,144 +0,0 @@
<%= board_breadcrumb(@message) %>
<div class="contextual">
<%= content_tag('span', watcher_link(@topic, User.current), :id => 'watcher') %>
<% voted = User.current.voted_for?(@topic) %>
<%= link_to(
voted ? l(:button_questions_unvote) : l(:button_questions_vote),
{:controller => 'questions', :action => 'vote', :id => @topic},
:class => 'icon ' + (voted ? 'icon-vote' : 'icon-unvote')
) if User.current.allowed_to?(:vote_messages, @project) %>
<%= link_to(
l(:button_quote),
{:action => 'quote', :id => @topic},
:remote => true,
:method => 'get',
:class => 'icon icon-comment',
:remote => true) if !@topic.locked? && authorize_for('messages', 'reply') %>
<%= link_to(
l(:button_edit),
{:action => 'edit', :id => @topic},
:class => 'icon icon-edit'
) if @message.editable_by?(User.current) %>
<%= link_to(
l(:button_delete),
{:action => 'destroy', :id => @topic},
:method => :post,
:data => {:confirm => l(:text_are_you_sure)},
:class => 'icon icon-del'
) if @message.destroyable_by?(User.current) %>
</div>
<div class="message details">
<h2><%=h @topic.subject %></h2>
<%= avatar(@topic.author, :size => "32") %>
<p class="author"><%= link_to_user @topic.author %><br>
<%= l(:label_questions_added_time, :value => time_tag(@topic.created_on)).html_safe %>
</p>
<div class="wiki">
<%= textilizable(@topic, :content) %>
</div>
<%= link_to_attachments @topic, :author => false %>
</div>
<br />
<% unless @replies.empty? %>
<h3 class="comments"><%= l(:label_reply_plural) %> (<%= @reply_count %>)</h3>
<% @replies.each do |message| %>
<div class="message reply" id="<%= "message-#{message.id}" %>">
<div class="contextual">
<% liked = User.current.voted_for?(message) %>
<%= link_to(
message.count_votes_up > 0 ? "(#{message.count_votes_up})" : "",
{:controller => 'questions', :action => 'vote', :id => message},
:class => 'vote icon ' + (liked ? 'icon-vote' : 'icon-unvote')
) if true || User.current.allowed_to?(:vote_messages, @project) %>
<%= link_to(
image_tag('comment.png'),
{:action => 'quote', :id => message},
:remote => true,
:method => 'get',
:title => l(:button_quote)) if !@topic.locked? && authorize_for('messages', 'reply') %>
<%= link_to(
image_tag('edit.png'),
{:action => 'edit', :id => message},
:title => l(:button_edit)
) if message.editable_by?(User.current) %>
<%= link_to(
image_tag('delete.png'),
{:action => 'destroy', :id => message},
:method => :post,
:data => {:confirm => l(:text_are_you_sure)},
:title => l(:button_delete)
) if message.destroyable_by?(User.current) %>
</div>
<% if Setting.gravatar_enabled? %>
<div class="avatar">
<%= message_avatar = avatar(message.author, :size => "32") %>
</div>
<% end %>
<div class="reply-details <%= 'use-avatar' unless message_avatar.blank? %>">
<h4 class="author"><%= authoring message.created_on, message.author %></h4>
<div class="wiki"><%= textilizable message, :content, :attachments => message.attachments %></div>
<%= link_to_attachments message, :author => false %>
</div>
</div>
<% end %>
<p class="pagination"><%= pagination_links_full @reply_pages, @reply_count, :per_page_links => false %></p>
<% end %>
<% if !@topic.locked? && authorize_for('messages', 'reply') %>
<p><%= toggle_link l(:button_reply), "reply", :focus => 'message_content' %></p>
<div id="reply" style="display:none;">
<%= form_for @reply, :as => :reply, :url => {:action => 'reply', :id => @topic}, :html => {:multipart => true, :id => 'message-form'} do |f| %>
<%= render :partial => 'form', :locals => {:f => f, :replying => true} %>
<%= submit_tag l(:button_submit) %>
<%= preview_link({:controller => 'messages', :action => 'preview', :board_id => @board}, 'message-form') %>
<% end %>
<div id="preview" class="wiki"></div>
</div>
<% end %>
<% content_for :sidebar do %>
<h3><%= l(:label_questions_message) %></h3>
<ul class="question-meta">
<li class="votes icon icon-vote">
<%= l(:label_questions_votes, :count => @topic.count_votes_up - @topic.count_votes_down ) %>
</li>
<li class="views icon icon-view">
<%= l(:label_questions_views, :count => @topic.view_count ) %>
</li>
<% unless @topic.tags.blank? %>
<li class="tags icon icon-tag">
<%=
@topic.tags.collect do |tag|
link_to tag, {:controller => "questions", :action => "topics", :project_id => @project, :tag => tag.name}
end.join(', ').html_safe
%>
</li>
<% end %>
</ul>
<h3><%= l(:label_questions_related_messages) %></h3>
<ul class="related-topics">
<%# Board.all.map(&:topics).flatten.first(5).each do |topic| %>
<% tokens = @topic.subject.strip.scan(%r{((\s|^)"[\s\w]+"(\s|$)|\S+)}).collect {|m| m.first.gsub(%r{(^\s*"\s*|\s*"\s*$)}, '')} || "" %>
<% if ActiveRecord::VERSION::MAJOR >= 4 %>
<% related_topics = Message.where(tokens.map{ |t| "subject LIKE '%#{t}%'" }.join(' OR ')).to_a.compact if tokens %>
<% else %>
<% related_topics = Message.search(tokens, @project, :limit => 5)[0].select{|m| m != @topic && m.parent_id == nil }.compact if tokens %>
<% end %>
<% related_topics.each do |topic| %>
<li class="related-topic">
<%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => topic.board, :id => topic } %>
</li>
<% end if related_topics %>
</ul>
<% end %>
<% html_title @topic.subject %>

View file

@ -0,0 +1,38 @@
<h3><%= l(:label_questions_sections_plural) %></h3>
<table class="list questions_sections">
<thead>
<tr>
<th><%= l(:field_name) %></th>
<th><%=l(:field_type)%></th>
<th></th>
</tr>
</thead>
<tbody>
<% QuestionsSection.for_project(@project).sorted.each do |section| %>
<tr class="<%= cycle 'odd', 'even' %>">
<td class="name">
<%= h(section.name) %>
</td>
<td>
<%= section.l_type %>
</td>
<td class="buttons">
<% if User.current.allowed_to?(:manage_sections, @project) %>
<%= reorder_handle(section, :url => project_questions_section_path(@project, section), :param => 'questions_section') if respond_to?(:reorder_handle) %>
<%= link_to l(:button_edit), {:controller => 'questions_sections', :action => 'edit', :project_id => @project, :id => section}, :class => 'icon icon-edit' %>
<%= delete_link :controller => 'questions_sections', :action => 'destroy', :project_id => @project, :id => section %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% if User.current.allowed_to?(:manage_sections, @project) %>
<%= link_to image_tag('add.png', :style => 'vertical-align: middle;')+l(:label_questions_section_new), :controller => 'questions_sections', :action => 'new', :project_id => @project %>
<% end %>
<%= javascript_tag do %>
$(function() { $("table.questions_sections tbody").positionedItems(); });
<% end if respond_to?(:reorder_handle) %>

View file

@ -0,0 +1,43 @@
<%= error_messages_for @question_item %>
<%= fields_for :question, @question_item do |f| %>
<div class="box">
<!--[form:message]-->
<p><label for="message_subject"><%= l(:field_subject) %></label><br />
<%= f.text_field :subject, :id => "question_subject", :size => 120%>
</p>
<p><label><%= l(:label_questions_section) %></label><br />
<%= f.select :section_id, options_from_collection_for_select(QuestionsSection.where(:project_id => @project),:id, :name, f.object.section_id), :style => "width: 80%;", :required => true %>
<%= javascript_tag do %>
$('#question_section_id').change(function() {
$.ajax({
url: '<%= escape_javascript update_form_questions_path(:id => @question_item, :format => 'js', :project_id => @project) %>',
type: 'put',
data: $('#question_form').serialize()
});
});
<% end %>
<%= link_to(image_tag('add.png', :style => 'vertical-align: middle;'),
(@project ? new_project_questions_section_url(:project_id => @project) : new_questions_section_url ),
:remote => true,
:method => 'get',
:title => l(:label_questions_section_new),
:class => "add_section",
:tabindex => 200) if User.current.allowed_to?(:manage_sections, @project) %>
</p>
<% if @question_item.is_idea? %>
<p><label><%= l(:field_status) %></label><br />
<%= f.select :status_id, options_from_collection_for_select(QuestionsStatus.sorted,:id, :name, @question_item.status_id ), :include_blank => true %>
</p>
<% end %>
<p>
<%= f.text_area :content, :cols => 80, :rows => 15, :class => 'wiki-edit', :id => 'question_content', :label => l(:label_questions_message) %>
</p>
<%= wikitoolbar_for 'question_content' %>
<!--[eoform:question]-->
<p>
<%= l(:label_attachment_plural) %><br />
<%= render :partial => 'attachments/form', :locals => {:container => @question_item} %>
</p>
</div>
<% end %>

View file

@ -1,23 +0,0 @@
<script type="text/javascript">
$(function(){
<% available_tags = RedmineCrm::Tag.joins(:taggings).joins("INNER JOIN messages ON taggings.taggable_id = messages.id AND taggings.taggable_type = 'Message'").joins("INNER JOIN boards ON messages.board_id = boards.id").where(["boards.project_id = ?", @project]) %>
var currentTags = ['<%= available_tags.map(&:name).join("\',\'").html_safe %>'];
$('#allowSpacesTags').tagit({
availableTags: currentTags,
allowSpaces: true,
caseSensitive: false,
removeConfirmation: true
});
});
</script>
<span class="message-tags-edit">
<%= text_field_tag 'message[tag_list]', "#{@message.tags.map(&:name).join(',').html_safe}", :size => 10, :class => 'hol', :id => "allowSpacesTags" %>
</span>
<% content_for :header_tags do %>
<%= javascript_include_tag :"tag-it", :plugin => 'redmine_questions' %>
<%= stylesheet_link_tag :"jquery.tagit.css", :plugin => 'redmine_questions' %>
<% end %>

View file

@ -1,68 +0,0 @@
<h2><%= l(:label_questions) %></h2>
<div class="filters">
<%= form_tag({:controller => "questions", :action => "topics"}, :method => :get, :id => "query_form") do %>
<%= hidden_field_tag('project_id', @project.to_param) if @project %>
<%= text_field_tag(:topic_search, params[:topic_search] , :autocomplete => "off", :class => "questions-search", :placeholder => l(:label_questions_search) ) %>
<%= javascript_tag "observeSearchfield('topic_search', 'forum_list', '#{ escape_javascript(autocomplete_for_topic_questions_path(:project_id => @project, :board_id => @board)) }')" %>
<% end %>
</div>
<div id="forum_list">
<% previous_group = false %>
<% boards = @project ? @boards : @boards.select{|b| b.topics_count > 0} %>
<% if @project %>
<ul>
<% end %>
<% boards.each do |board| %>
<% cache(Message.last.updated_on.to_s + board.id.to_s) do %>
<% if @project.blank? && (group = board.project) != previous_group %>
<% reset_cycle %>
</ul>
<div class="project-forums">
<h3><%= group.blank? ? 'None' : group.name %><%= link_to " \xc2\xbb", project_boards_path(:project_id => group) %></h3>
</div>
<ul>
<% previous_group = group %>
<% end %>
<li class="<%= cycle('odd', 'even') %> ">
<h3>
<%= link_to h(board.name), {:controller => "boards", :action => 'show', :id => board, :project_id => board.project_id}, :class => "board" %>
<span class="topic-count"><%= "(#{board.topics.count})" %></span>
</h3>
<div class="topic-list">
<% board.topics.sort_by{|m| [m.sticky, m.updated_on] }.reverse.first(5).each do |topic| %>
<div class="list-item">
<span class="topic-subject">
<%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => board, :id => topic } %>
</span><br>
<span class="last-author">
<% last_update = [topic.last_reply ? topic.last_reply.updated_on : topic.created_on, topic.updated_on].max %>
<% last_author = (topic.last_reply && topic.last_reply.updated_on) ? topic.last_reply.author : topic.author %>
<%= authoring last_update, last_author, :label => :label_updated_time_by %><br />
</span>
</div>
<% end %>
</div>
</li>
<% end %>
<% end %>
</ul>
</div>
<% content_for :sidebar do %>
<%= render :partial => "questions/notice" %>
<%= render :partial => "questions/tag_cloud" %>
<%= render :partial => "questions/latest_topics" %>
<%= render :partial => "questions/voted_topics" %>
<% end %>
<% content_for :header_tags do %>
<%= javascript_include_tag :questions, :plugin => 'redmine_questions' %>
<% end %>

View file

@ -1,14 +1,8 @@
<%
scope = Message.where({})
scope = scope.where("#{Message.table_name}.parent_id IS NULL")
scope = scope.where(["#{Board.table_name}.project_id = ?", @project.id]) if @project
@latest_topics = scope.visible.includes(:board).order("#{Message.table_name}.created_on DESC").limit(5)
%>
<h3><%= l(:label_questions_latest_messages) %></h3>
<ul class="related-topics">
<% @latest_topics.each do |topic| %>
<li class="related-topic">
<%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => topic.board, :id => topic } %>
</li>
<% end unless @latest_topics.blank? %>
</ul>
<% Question.visible.by_date.in_project(@project).limit(5).each do |question| %>
<li class="related-topic">
<%= link_to h(question.subject), { :controller => 'questions', :action => 'show', :id => question, :project_id => @project } %>
</li>
<% end %>
</ul>

View file

@ -1,3 +0,0 @@
<% unless Setting.plugin_redmine_questions[:sidebar_message].blank? %>
<div class="wiki"><%= textilizable(Setting.plugin_redmine_questions[:sidebar_message]) %></div>
<% end %>

View file

@ -0,0 +1,18 @@
<%
@popular_topics = Question.
visible.
in_project(@project).
in_section(@section).
positive.
by_views.
by_votes.
limit(5)
%>
<h3><%= l(:label_questions_most_popular) %></h3>
<ul class="related-topics">
<% @popular_topics.each do |question| %>
<li class="related-topic">
<%= link_to h(question.subject), { :controller => 'questions', :action => 'show', :section_id => question.section, :id => question } %>
</li>
<% end unless @popular_topics.blank? %>
</ul>

View file

@ -0,0 +1,65 @@
<div class="contextual">
<%= content_tag('span', watcher_link(@question_item, User.current), :id => 'watcher') %>
<%= link_to(l(:button_edit), edit_question_path(@question_item), :class => 'icon icon-edit' ) if @question_item.editable_by?(User.current)
%>
<%= link_to(l(:button_delete), question_path(@question_item), :method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :class => 'icon icon-del') if @question_item.destroyable_by?(User.current)
%>
</div>
<h1 class="question-title"><%=h @question_item.subject %></h1>
<%= render :partial => 'question_item', :object => @question_item %>
<% if @question_item.section.allow_answering? %>
<div id="answers">
<% if @answers.any? %>
<h3><%= l(:label_questions_answer_plural) %> (<%= @answer_count %>)</h3>
<% @answers.each do |answer| %>
<%= render :partial => 'questions_answers/answer_item', :locals => { :question_item => answer } %>
<% end %>
<span class="pagination"><%= pagination_links_full @answer_pages, @answer_count %></span>
<% end %>
<% if @question_item.allow_answering? && User.current.allowed_to?(:add_answers, @project) %>
<h3><%= l(:label_questions_your_answer) %></h3>
<div id="reply" >
<%= form_for @answer, :as => :answer, :url => question_answers_path(@question_item), :html => {:multipart => true, :id => 'answer-form'} do |f| %>
<%= render :partial => 'questions_answers/form', :locals => {:f => f, :replying => true} %>
<%= submit_tag l(:button_submit) %>
<% end %>
<div id="preview" class="wiki"></div>
</div>
<% end %>
</div>
<% end %>
<% content_for :sidebar do %>
<h3><%= l(:label_questions_message) %></h3>
<ul class="question-meta">
<li class="views icon icon-view">
<%= l(:label_questions_views, :count => @question_item.views ) %>
</li>
</ul>
<% if @question_item.convertable_by?(User.current) && User.current.allowed_to?(:add_issues, @project) %>
<h3><%= l(:label_questions_actions) %></h3>
<ul class="action">
<li>
<%= link_to(
l(:button_questions_to_issue),
convert_to_issue_project_question_path(@project, @question_item)
)
%>
</li>
</ul>
<% end %>
<h3><%= l(:label_questions_related_questions) %></h3>
<ul class="related-topics">
<% Question.visible.related(@question_item, 5).each do |question| %>
<li class="related-topic">
<%= link_to h(question.subject), { :controller => 'questions', :action => 'show', :board_id => nil, :id => question } %>
</li>
<% end %>
</ul>
<% end %>

View file

@ -0,0 +1,23 @@
<div class="question<%= " votable" if question_item.allow_voting? %> div-table" id="question_<%= question_item.id %>">
<a href="#<%= question_item.id %>" class="wiki-anchor"></a>
<% if question_item.allow_voting? && User.current.allowed_to?(:vote_questions, @project) %>
<div class="vote">
<%= render :partial => 'questions_votes/question_item_vote', :locals => {:question_item => question_item} %>
</div>
<% end %>
<div class="question-container div-table-cell">
<%= avatar(question_item.author, :size => "32") %>
<p class="author">
<%= link_to_user question_item.author %><br>
<%= l(:label_questions_added_time, :value => time_tag(question_item.created_on)).html_safe %>
</p>
<div class="wiki">
<%= textilizable(question_item, :content) %>
</div>
<%= link_to_attachments question_item, :author => false %>
<%= render :partial => 'questions_comments/comments_container', :locals => { :question_item => question_item } %>
</div>
</div>

View file

@ -0,0 +1,47 @@
<% if @question_items && @question_items.any? %>
<% unless params[:tag].blank? %>
<div class="title-bar">
<h4><%= l(:label_questions_tagged_by, :count => @question_items.size, :tag => params[:tag]) %></h4>
</div>
<% end %>
<div id="forum_list">
<div id="topics_container" class="<%= " votable" if @section && @section.allow_voting? %>">
<% @question_items.each do |question| %>
<div class="topic">
<% if @section && @section.allow_voting? %>
<div class="topic-vote">
<span class="vote-score"><%= question.weighted_score %></span>
<label><%= l(:label_questions_x_votes, :count => question.weighted_score.abs) %></label>
<% if question.answered? %>
<div class="status-answered" title="Answered"></div>
<% end %>
</div>
<% end %>
<div class="topic-content">
<h3 class="subject">
<%= link_to h(question.subject), { :controller => 'questions', :action => 'show', :project_id => question.project, :id => question } %>
<%= question_status_tag(question.status) %>
</h3>
<p><%= truncate(Question.to_text(textilizable(question.content)), :length => 100) %></p>
<ul class="meta">
<% if question.allow_answering? %>
<li class="answers icon icon-comment"><%= l(:label_questions_answers, :count => question.answers_count) %></li>
<% end %>
<li class="views icon icon-view"><%= l(:label_questions_views, :count => question.views ) %></li>
</ul>
</div>
</div>
<% end %>
</div>
</div>
<% if @topic_pages %>
<% params[:controller] = 'questions'
params[:action] = 'topics'
%>
<p class="pagination"><%= pagination_links_full @topic_pages, @topic_count %></p>
<% end %>
<% else %>
<p style="display: inline-block"></p>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>

View file

@ -1,35 +0,0 @@
<%
limit = 30
scope = RedmineCrm::Tag.where({})
scope = scope.where("#{Project.table_name}.id = ?", @project) if @project
scope = scope.where(Project.allowed_to_condition(User.current, :view_messages))
join = []
join << "JOIN #{RedmineCrm::Tagging.table_name} ON #{RedmineCrm::Tagging.table_name}.tag_id = #{RedmineCrm::Tag.table_name}.id "
join << "JOIN #{Message.table_name} ON #{Message.table_name}.id = #{RedmineCrm::Tagging.table_name}.taggable_id AND #{RedmineCrm::Tagging.table_name}.taggable_type = '#{Message.name}' "
join << "JOIN #{Board.table_name} ON #{Board.table_name}.id = #{Message.table_name}.board_id"
join << "JOIN #{Project.table_name} ON #{Project.table_name}.id = #{Board.table_name}.project_id"
group_fields = ""
group_fields << ", #{RedmineCrm::Tag.table_name}.created_at" if RedmineCrm::Tag.respond_to?(:created_at)
group_fields << ", #{RedmineCrm::Tag.table_name}.updated_at" if RedmineCrm::Tag.respond_to?(:updated_at)
scope = scope.joins(join.join(' '))
scope = scope.select("#{RedmineCrm::Tag.table_name}.*, COUNT(DISTINCT #{RedmineCrm::Tagging.table_name}.taggable_id) AS count")
scope = scope.group("#{RedmineCrm::Tag.table_name}.id, #{RedmineCrm::Tag.table_name}.name #{group_fields} HAVING COUNT(*) > 0")
scope = scope.order("#{RedmineCrm::Tag.table_name}.name")
scope = scope.limit(limit) if limit
@available_tags = scope
%>
<h3><%= l(:label_questions_tags) %></h3>
<ul class="questions-tags">
<% @available_tags.each do |tag| %>
<li>
<%= link_to tag, {:controller => "questions", :action => "topics", :project_id => @project, :tag => tag.name} %>
<span class="count"><%= tag.count %></span>
</li>
<% end if @available_tags %>
</ul>

View file

@ -1,16 +1,16 @@
<%
scope = Message.scoped({})
scope = scope.where("#{Message.table_name}.parent_id IS NULL")
scope = scope.where(["#{Board.table_name}.project_id = ?", @project.id]) if @project
scope = scope.where(["#{Message.table_name}.board_id = ?", @board.id]) if @board
scope = scope.where(:sticky => true)
@sticky_topics = scope.visible.includes(:board).order("#{Message.table_name}.cached_votes_up DESC").limit(10)
# scope = Message.scoped({})
# scope = scope.where("#{Message.table_name}.parent_id IS NULL")
# scope = scope.where(["#{Board.table_name}.project_id = ?", @project.id]) if @project
# scope = scope.where(["#{Message.table_name}.board_id = ?", @board.id]) if @board
# scope = scope.where(:featured => true)
# @featured_topics = scope.visible.includes(:board).order("#{Message.table_name}.cached_votes_up DESC").limit(10)
%>
<h3><%= l(:label_questions_most_voted) %></h3>
<ul class="related-topics">
<% @sticky_topics.each do |topic| %>
<% @featured_questions.each do |question| %>
<li class="related-topic">
<%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => topic.board, :id => topic } %>
<%= link_to h(question.subject), { :controller => 'questions', :action => 'show', :project_id => question.project, :id => question } %>
</li>
<% end unless @sticky_topics.blank? %>
</ul>
<% end unless @featured_topics.blank? %>
</ul>

View file

@ -1,8 +1,8 @@
<% if @topics && @topics.any? %>
<% unless params[:tag].blank? %>
<div class="title-bar">
<h4><%= l(:label_questions_tagged_by, :count => @topics.size, :tag => params[:tag]) %></h4>
</div>
<div class="title-bar">
<h4><%= l(:label_questions_tagged_by, :count => @topics.size, :tag => params[:tag]) %></h4>
</div>
<% end %>
<div id="topics_container">
<% @topics.each do |topic| %>
@ -26,5 +26,5 @@
<p class="pagination"><%= pagination_links_full @topic_pages, @topic_count %></p>
<% end %>
<% else %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>
<p class="nodata"><%= l(:label_no_data) %></p>
<% end %>

View file

@ -1,16 +0,0 @@
<%
scope = Message.where({})
scope = scope.where("#{Message.table_name}.parent_id IS NULL")
scope = scope.where(["#{Board.table_name}.project_id = ?", @project.id]) if @project
scope = scope.where(["#{Message.table_name}.board_id = ?", @board.id]) if @board
scope = scope.where("#{Message.table_name}.cached_votes_up > 0")
@most_voted_topics = scope.visible.includes(:board).order("#{Message.table_name}.cached_votes_up DESC").limit(5)
%>
<h3><%= l(:label_questions_most_voted) %></h3>
<ul class="related-topics">
<% @most_voted_topics.each do |topic| %>
<li class="related-topic">
<%= link_to h(topic.subject), { :controller => 'messages', :action => 'show', :board_id => topic.board, :id => topic } %>
</li>
<% end unless @most_voted_topics.blank? %>
</ul>

View file

@ -0,0 +1 @@
<%= render :partial => "questions/question_list" %>

View file

@ -1 +0,0 @@
<%= render :partial => "questions/topic_list" %>

View file

@ -0,0 +1,15 @@
<h2><%= avatar(@question_item.author, :size => "24") %><%=h @question_item.subject %></h2>
<%= form_for @question_item, { :url => question_path(@question_item), :html => {:multipart => true,
:id => 'question_form', :method => :put}} do |f| %>
<%= back_url_hidden_field_tag %>
<div id="all_attributes">
<%= render :partial => 'form', :locals => {:f => f} %>
</div>
<%= submit_tag l(:button_save) %>
<%= preview_link({:controller => 'questions', :action => 'preview', :question_id => @question_item}, 'question_form') %>
<% end %>
<div id="preview" class="wiki"></div>

View file

@ -0,0 +1,35 @@
<% html_title l(:label_questions) %>
<div class="questions" >
<div class="contextual">
<%= link_to(l(:label_questions_new),
{:controller => 'questions', :action => 'new', :section_id => @section},
:class => 'icon icon-add') if User.current.allowed_to?(:add_questions, @project) %>
<%= link_to(l(:label_questions_section_edit),
{:controller => 'questions_sections', :action => 'edit', :id => @section},
:class => 'icon icon-edit') if @section && User.current.allowed_to?(:manage_sections, @project) %>
</div>
<%= question_breadcrumb @section %>
<h2 class="section-title">
<%= @section ? @section.name : l(:label_questions)%>
</h2>
<% if @section && !@section.description.blank? %>
<em class="info"><%= @section.description %></em>
<% end %>
<div class="filters">
<%= form_tag({:controller => "questions", :action => "index"}, :method => :get, :id => "query_form") do %>
<%= text_field_tag(:topic_search, params[:topic_search], :autocomplete => "off", :class => "questions-search", :placeholder => l(:label_questions_search) ) %>
<%= javascript_tag "observeSearchfield('topic_search', 'topics_list', '#{ escape_javascript(autocomplete_for_subject_questions_path(:project_id => @project, :section_id => @section)) }')" %>
<% end %>
</div>
</div>
<div id="topics_list" >
<%= render :partial => "questions/question_list" %>
</div>
<% content_for :sidebar do %>
<%= render :partial => "questions/latest_topics" %>
<%= render :partial => "questions/popular_topics" %>
<% end %>

View file

@ -1,9 +1,12 @@
<h2><%= l(:label_message_new) %></h2>
<%= form_for @message, :url => {:controller => "messages", :action => 'new'}, :html => {:multipart => true, :id => 'message-form'} do |f| %>
<%= render :partial => 'messages/form', :locals => {:f => f} %>
<%= form_for @question_item, { :url => questions_path, :html => {:multipart => true,
:id => 'question_form'}} do |f| %>
<div id="all_attributes">
<%= render :partial => 'form', :locals => {:f => f} %>
</div>
<%= submit_tag l(:button_create) %>
<%# preview_link({:controller => 'messages', :action => 'preview', :board_id => @board}, 'message-form') %>
<% preview_link({:controller => 'questions', :action => 'preview', :id => @question_item}, 'question_form') %>
<% end %>
<div id="preview" class="wiki"></div>

View file

@ -0,0 +1,6 @@
<%= question_breadcrumb @question_item %>
<%= render :partial => 'question' if QA_VERSION_TYPE.match(/Light/) %>
<% html_title @question_item.subject %>

View file

@ -1,31 +0,0 @@
<% if @board %>
<%= board_breadcrumb(@board) %>
<div class="board details">
<h2><%=h @board.name %></h2>
<p class="subtitle"><%=h @board.description %></p>
<% else %>
<h2><%= l(:label_questions) %></h2>
<% end %>
<div class="filters">
<%= form_tag({:controller => "questions", :action => "topics"}, :method => :get, :id => "query_form") do %>
<%= hidden_field_tag('project_id', @project.to_param) if @project %>
<%= hidden_field_tag('board_id', @board.to_param) if @board %>
<%= text_field_tag(:topic_search, params[:topic_search], :autocomplete => "off", :class => "questions-search", :placeholder => l(:label_questions_search) ) %>
<%= javascript_tag "observeSearchfield('topic_search', 'topics_list', '#{ escape_javascript(autocomplete_for_topic_questions_path(:project_id => @project, :board_id => @board)) }')" %>
<% end %>
</div>
<% if @board %>
</div>
<% end %>
<div id="topics_list" >
<%= render :partial => "questions/topic_list" %>
</div>
<% content_for :sidebar do %>
<%= render :partial => "questions/latest_topics" %>
<% end %>

View file

@ -0,0 +1 @@
$('#all_attributes').html('<%= escape_javascript(render :partial => 'form') %>');

View file

@ -0,0 +1,5 @@
<div class="contextual">
<%= link_to(l(:button_edit), edit_questions_answer_path(question_item), :class => 'icon icon-edit') if question_item.editable_by?(User.current) %>
<%= link_to(l(:button_delete), questions_answer_path(question_item), :method => :delete, :data => {:confirm => l(:text_are_you_sure)}, :class => 'icon icon-del') if question_item.destroyable_by?(User.current)
%>
</div>

View file

@ -0,0 +1,23 @@
<div class="question answer<%= ' votable' if question_item.allow_voting? %> div-table" id="questions_answer_<%= question_item.id %>">
<a href="#<%= question_item.id %>" class="wiki-anchor"></a>
<% if question_item.allow_voting? && User.current.allowed_to?(:vote_questions, @project) %>
<div class="vote" >
<%= render :partial => 'questions_votes/question_item_vote', :locals => {:question_item => question_item } %>
</div>
<% end %>
<div class="question-container div-table-cell">
<%= render :partial => "questions_answers/actions", :locals => { :question_item => question_item } %>
<%= avatar(question_item.author, :size => "32") %>
<p class="author">
<%= link_to_user question_item.author %><br>
<%= l(:label_questions_added_time, :value => time_tag(question_item.created_on)).html_safe %>
</p>
<div class="wiki">
<%= textilizable(question_item, :content) %>
</div>
<%= link_to_attachments question_item, :author => false %>
</div>
</div>

View file

@ -0,0 +1,10 @@
<%= error_messages_for @answer %>
<div class="box">
<%= f.text_area :content, :cols => 80, :rows => 15, :class => 'wiki-edit', :id => 'answer_content', :no_label => true %>
<%= wikitoolbar_for 'answer_content' %>
<p>
<%= l(:label_attachment_plural) %><br />
<%= render :partial => 'attachments/form', :locals => {:container => @answer} %>
</p>
</div>

View file

@ -0,0 +1,12 @@
<h2><%= avatar(@answer.author, :size => "24") %><%=h @answer.question.subject %></h2>
<%= form_for @answer, :as => :answer, :url => questions_answer_path(@answer), :html => {:multipart => true, :id => 'answer-form', :method => :put} do |f| %>
<%= back_url_hidden_field_tag %>
<%= render :partial => 'form', :locals => {:f => f} %>
<%= submit_tag l(:button_save) %>
<%= preview_link(preview_questions_answers_path(@answer), 'answer-form') %>
<% end %>
<div id="preview" class="wiki"></div>

View file

@ -0,0 +1,27 @@
<div class="comment" id="comment_<%= comment.id %>">
<div class="contextual">
<%= link_to(
"",
{:controller => 'questions_comments', :action => 'edit', :source_id => comment.commented, :source_type => comment.commented.class.name.underscore, :id => comment.id},
:class => 'icon icon-edit',
:method => :get
) if (User.current.allowed_to?(:edit_question_comments, comment.commented.project) || (comment.author == User.current && User.current.allowed_to?(:edit_own_question_comments, comment.commented.project)))
%>
<%= link_to(
"",
{:controller => 'questions_comments', :action => 'destroy', :source_id => comment.commented, :id => comment, :source_type => comment.commented.class.name.underscore}, :class => 'icon icon-del',
:data => {:confirm => l(:text_are_you_sure)},
:method => :delete,
:title => l(:button_delete)
) if (User.current.allowed_to?(:edit_question_comments, comment.commented.project) || (comment.author == User.current && User.current.allowed_to?(:edit_own_question_comments, comment.commented.project)))
%>
</div>
<div class="author">
<%= link_to_user comment.author %>
<%= time_tag(comment.created_on) %>
</div>
<div class="wiki-content">
<%= textilizable(comment.comments) %>
</div>
</div>

View file

@ -0,0 +1,7 @@
<% if question_item.commentable? %>
<%= form_tag({:controller => 'questions_comments', :action => 'create', :id => question_item, :source_id => question_item, :source_type => question_item.class.name.underscore}, :class => "add-comment-form", :remote => true) do %>
<%= text_area 'comment', @comment.respond_to?(:content) ? 'content' : 'comments', :cols => 80, :rows => 5, :id => "comments_for_#{question_item.class.name.underscore}_#{question_item.id}" %>
<%= submit_tag l(:button_add), :class => "button-small" %>
<%= link_to l(:button_cancel), {}, :onclick => "$('#add_#{question_item.class.name.underscore}_comments_#{question_item.id}').hide(); return false;" %>
<% end %>
<% end %>

View file

@ -0,0 +1,12 @@
<% if question_item.comments.any? %>
<div class="question-comments">
<% question_item.comments.each do |comment| %>
<% next if comment.new_record? %>
<%= render :partial => "questions_comments/comment", :locals => {:comment => comment, :comment_source => question_item} %>
<% end %>
</div>
<% end %>
<div class="add_comments" id="add_<%= question_item.class.name.underscore %>_comments_<%= question_item.id %>" style="display:none;">
<%= render :partial => "questions_comments/comment_form", :locals => {:question_item => question_item} %>
</div>

View file

@ -0,0 +1,7 @@
<% if question_item.commentable? %>
<%= link_to l(:label_questions_comment), "#", :onclick => "$('#add_comments_#{question_item.id}').toggle(); showAndScrollTo('add_#{question_item.class.name.underscore}_comments_#{question_item.id}', 'comments_for_#{question_item.class.name.underscore}_#{question_item.id}'); return false;", :class => 'icon icon-comment add-comment-link' %>
<% end %>
<div class="comments_container">
<%= render :partial => 'questions_comments/comment_list', :locals => {:question_item => question_item} %>
</div>

View file

@ -0,0 +1,3 @@
$("#<%= @comment_source.class.name.underscore %>_<%= @comment_source.id %> .comments_container").html('<%= escape_javascript(render :partial => "questions_comments/comment_list", :locals => {:question_item => @comment_source}) %>');
$(".comment#comment_<%= @comment.id %>").effect('highlight', {}, 1000);
$("textarea#comments_for_<%= @comment_source.class.name.underscore %>_<%= @comment_source.id %>").val("");

View file

@ -0,0 +1,12 @@
<h2><%= avatar(@comment.author, :size => "24") %></h2>
<%= form_tag({:controller => 'questions_comments', :action => 'update', :source_id => @comment_source, :source_type => @comment_source.class.name.underscore, :id => @comment, :method => :put}) do %>
<div class="box">
<%= text_area 'comment', 'comments', :cols => 80, :rows => 5%>
</div>
<p><%= submit_tag l(:button_update) %></p>
<% end %>
<div id="preview" class="wiki"></div>

View file

@ -0,0 +1,7 @@
<%= error_messages_for @section %>
<p>
<%= f.text_field :name, :required => true %>
</p>
<p>
<%= f.text_area :description, :rows => 5 %>
</p>

View file

@ -0,0 +1,8 @@
<h3 class="title"><%=l(:label_questions_section_new) %></h3>
<%= labelled_form_for((@project ? [@project, @section] : @section), :remote => true, :html => {:class => 'tabular'}) do |f|%>
<%= render :partial => 'form', :locals => { :f => f} %>
<p>
<%= f.submit l(:button_create) %>
</p>
<% end %>

View file

@ -0,0 +1,29 @@
<% previous_group = false %>
<div class="section-list">
<% @sections.each do |section| %>
<% if @project.blank? && (group = section.project) != previous_group %>
<% reset_cycle %>
</div>
<% if group %>
<div class="project-forums">
<h3>
<%= group.name %>
<%= link_to " \xc2\xbb", project_questions_sections_path(:project_id => group.identifier) %>
</h3>
</div>
<% end %>
<div class="section-list">
<% previous_group = group %>
<% end %>
<a href="<%= url_for({:controller => "questions", :action => 'index', :section_id => section, :project_id => section.project}) %>" id="section_<%= section.id %>" class="section-tile">
<h4>
<%= section.name %>
<span class="topic-count"><%= "(#{section.questions_count})" %></span>
</h4>
<div class="description">
<%= section.description %>
</div>
</a>
<% end %>
</ul>

View file

@ -0,0 +1,8 @@
hideModal();
<% select = content_tag('select', content_tag('option') + options_from_collection_for_select(QuestionsSection.where(:project_id => @project), :id, :name, @section.id.to_s), :id => 'question_section_id', :name => 'question[section_id]') %>
$('#question_section_id').replaceWith('<%= escape_javascript(select) %>');
$.ajax({
url: '<%= escape_javascript update_form_questions_path(:id => @question_item, :format => 'js', :project_id => @project) %>',
type: 'put',
data: $('#question_form').serialize()
});

View file

@ -0,0 +1,11 @@
<h2><%=l(:label_questions_section)%></h2>
<%= labelled_form_for (@project ? [@project, @section] : @section), :method => "PUT", :html => {:class => 'tabular'} do |f|%>
<div class="box tabular">
<%= back_url_hidden_field_tag %>
<%= render :partial => 'form', :locals => { :f => f} %>
</div>
<p>
<%= f.submit l(:button_save) %>
</p>
<% end %>

View file

@ -0,0 +1,29 @@
<% html_title l(:label_questions) %>
<div class="contextual">
<%= link_to(l(:label_questions_new),
{:controller => 'questions', :action => 'new', :section_id => @section},
:class => 'icon icon-add') if User.current.allowed_to?(:add_questions, @project) %>
</div>
<h2 class=""><%= l(:label_questions) %></h2>
<div class="filters">
<%= form_tag({:controller => "questions", :action => "index"}, :method => :get, :id => "query_form") do %>
<%= text_field_tag(:topic_search, params[:topic_search], :autocomplete => "off", :class => "questions-search", :placeholder => l(:label_questions_search) ) %>
<%= javascript_tag "observeSearchfield('topic_search', 'forum_list', '#{ escape_javascript(autocomplete_for_subject_questions_path(:project_id => @project, :section_id => @section)) }')" %>
<% end %>
</div>
<div id="forum_list">
<%= render :partial => 'tiles' %>
</div>
<% content_for :sidebar do %>
<%= render :partial => "questions/latest_topics" %>
<%= render :partial => "questions/popular_topics" %>
<% end %>
<% content_for :header_tags do %>
<%= javascript_include_tag :questions, :plugin => 'redmine_questions' %>
<% end %>

View file

@ -0,0 +1,11 @@
<h2><%=l(:label_questions_section_new)%></h2>
<%= labelled_form_for((@project ? [@project, @section] : @section), :html => {:class => 'tabular'}) do |f|%>
<div class="box tabular">
<%= back_url_hidden_field_tag %>
<%= render :partial => 'form', :locals => { :f => f} %>
</div>
<p>
<%= f.submit l(:button_create) %>
</p>
<% end %>

View file

@ -0,0 +1,2 @@
$('#ajax-modal').html('<%= escape_javascript(render :partial => 'new_modal') %>');
showModal('ajax-modal', '600px');

View file

@ -0,0 +1,14 @@
<%= error_messages_for 'questions_status' %>
<div class="box tabular">
<p><%= f.text_field :name, :required => true %></p>
<p><%= f.select :color, options_for_select(QuestionsSettings::IDEA_COLORS, @questions_status.color), {:label => l(:field_color)} %></p>
<p><%= f.check_box :is_closed, :label => :label_questions_status_closed %></p>
</div>
<%= javascript_tag "$('#questions_status_color').simplecolorpicker({picker: true});"%>
<% content_for :header_tags do %>
<%= javascript_include_tag 'jquery.simplecolorpicker.js', :plugin => "redmine_questions" %>
<%= stylesheet_link_tag 'jquery.simplecolorpicker.css', :plugin => 'redmine_questions' %>
<% end %>

View file

@ -0,0 +1,6 @@
<h2><%= link_to l(:label_questions_status_plural), :action =>"plugin", :id => "redmine_questions", :controller => "settings", :tab => 'questions_statuses' %> &#187; <%=h @questions_status %></h2>
<%= labelled_form_for @questions_status do |f| %>
<%= render :partial => 'form', :locals => {:f => f} %>
<%= submit_tag l(:button_save) %>
<% end %>

View file

@ -0,0 +1,10 @@
api.array :questions_statuses do
@questions_statuses.each do |status|
api.questions_status do
api.id status.id
api.name status.name
api.color status.color
api.is_closed status.is_closed
end
end
end

View file

@ -0,0 +1,6 @@
<h2><%= link_to l(:label_questions_status_plural), :action =>"plugin", :id => "redmine_questions", :controller => "settings", :tab => 'questions_statuses' %> &#187; <%=l(:label_questions_status_new)%></h2>
<%= labelled_form_for @questions_status do |f| %>
<%= render :partial => 'form', :locals => {:f => f} %>
<%= submit_tag l(:button_create) %>
<% end %>

View file

@ -1,3 +0,0 @@
<p><label for="settings_sidebar_message"><%= l(:label_questions_sidebar_message) %></label>
<%= text_area_tag 'settings[sidebar_message]', @settings[:sidebar_message], :class => 'wiki-edit', :rows => 5 %>
</p>