Nuevo plugin Redmine CKEditor 1.1.5

This commit is contained in:
Manuel Cillero 2018-02-02 22:25:19 +01:00
parent 64924a6376
commit 698e4e7c3c
635 changed files with 24046 additions and 0 deletions

View file

@ -0,0 +1,5 @@
Description:
Generate rich asset files for Redmine
Example:
rails generate redmine_ckeditor:rich_assets

View file

@ -0,0 +1,35 @@
require 'rake'
module RedmineCkeditor
class RichAssetsGenerator < Rails::Generators::Base
source_root File.expand_path('../templates', __FILE__)
desc "Generate rich asset files for Redmine"
def create_assets
rake "redmine_ckeditor:assets:copy"
gsub_file RedmineCkeditor.root.join("assets/ckeditor-contrib/plugins/richfile/plugin.js"),
"/assets/rich/", "../images/"
application_js = RedmineCkeditor.root.join("assets/javascripts/application.js")
browser_js = RedmineCkeditor.root.join("assets/javascripts/browser.js")
gsub_file browser_js, "opt=opt.split(',');", "opt=opt ? opt.split(',') : [];"
gsub_file application_js, /var CKEDITOR_BASEPATH.+$/, ""
gsub_file application_js, /CKEDITOR.plugins.addExternal.+$/, ""
gsub_file browser_js, '"/rich/files/"+', ""
inject_into_file browser_js,
"\t\turl = $(item).data('relative-url-root') + url;\n",
:after => "data('uris')[this._options.currentStyle];\n"
gsub_file RedmineCkeditor.root.join("assets/stylesheets/application.css"),
'image-url("rich/', 'url("../images/'
append_to_file RedmineCkeditor.root.join("assets/stylesheets/editor.css"),
"\nhtml, body {\n height: 100%;\n}\n"
end
end
end

View file

@ -0,0 +1,143 @@
module RedmineCkeditor
extend ActionView::Helpers
class << self
def root
@root ||= Pathname(File.expand_path(File.dirname(File.dirname(__FILE__))))
end
def assets_root
@assets_root ||= "#{Redmine::Utils.relative_url_root}/plugin_assets/redmine_ckeditor"
end
def allowed_protocols
@allowed_protocols ||= ckeditor_config[:allowedProtocols] || %w[
afs aim callto ed2k feed ftp gopher http https irc mailto news
nntp rsync rtsp sftp ssh tag telnet urn webcal xmpp
]
end
def allowed_tags
@allowed_tags ||= ckeditor_config[:allowedTags] || %w[
a abbr acronym address blockquote b big br caption cite code dd del dfn
div dt em h1 h2 h3 h4 h5 h6 hr i img ins kbd li ol p pre samp small span
strike s strong sub sup table tbody td tfoot th thead tr tt u ul var iframe
]
end
def allowed_attributes
@allowed_attributes ||= ckeditor_config[:allowedAttributes] || %w[
href src width height alt cite datetime title class name xml:lang abbr dir
style align valign border cellpadding cellspacing colspan rowspan nowrap
start reversed
]
end
def default_toolbar
@default_toolbar ||= %w[
Source ShowBlocks -- Undo Redo - Find Replace --
Bold Italic Underline Strike - Subscript Superscript -
NumberedList BulletedList - Outdent Indent Blockquote -
JustifyLeft JustifyCenter JustifyRight JustifyBlock -
Link Unlink - richImage Table HorizontalRule
/
Styles Format Font FontSize - TextColor BGColor
].join(",")
end
def config
ActionController::Base.config
end
def plugins
@plugins ||= Dir.glob(root.join("assets/ckeditor-contrib/plugins/*")).map {
|path| File.basename(path)
}
end
def skins
@skins ||= Dir.glob(root.join("assets/ckeditor-contrib/skins/*")).map {
|path| File.basename(path)
}
end
def skin_options
options_for_select(["moono-lisa"] + skins, :selected => RedmineCkeditorSetting.skin)
end
def enter_mode_options
options_for_select({:p => 1, :br => 2, :div => 3},
:selected => RedmineCkeditorSetting.enter_mode)
end
def toolbar_location_options
options_for_select(["top", "bottom"],
:selected => RedmineCkeditorSetting.toolbar_location)
end
def ckeditor_config
@ckeditor_config ||= begin
conf = {
:extraPlugins => plugins.join(","),
:allowedContent => true,
:bodyClass => "wiki",
:removePlugins => 'div,flash,forms,iframe',
:forcePasteAsPlainText => false
}
file = Rails.root.join("config/ckeditor.yml")
conf.merge!(YAML.load_file(file).symbolize_keys) if file.exist?
conf
end
end
def options(scope_object = nil)
scope_type = scope_object && scope_object.class.model_name.name
scope_id = scope_object && scope_object.id
skin = RedmineCkeditorSetting.skin
skin += ",#{assets_root}/ckeditor-contrib/skins/#{skin}/" if skin != "moono-lisa"
rich_options = Rich.options({
:contentsCss => [stylesheet_path("application"), "#{assets_root}/stylesheets/editor.css"],
:scoped => scope_object ? true : false,
:allow_document_uploads => true,
:allow_embeds => true,
:default_style => :original,
:richBrowserUrl => "#{Redmine::Utils.relative_url_root}/rich/files/"
}, scope_type, scope_id)
rich_options.delete(:removeDialogTabs)
rich_options.delete(:format_tags)
rich_options.delete(:stylesSet)
rich_options.merge(ckeditor_config.merge({
:skin => skin,
:uiColor => RedmineCkeditorSetting.ui_color,
:enterMode => RedmineCkeditorSetting.enter_mode,
:shiftEnterMode => RedmineCkeditorSetting.shift_enter_mode,
:startupOutlineBlocks => RedmineCkeditorSetting.show_blocks,
:toolbarCanCollapse => RedmineCkeditorSetting.toolbar_can_collapse,
:toolbarStartupExpanded => !RedmineCkeditorSetting.toolbar_can_collapse,
:toolbarLocation => RedmineCkeditorSetting.toolbar_location,
:toolbar => RedmineCkeditorSetting.toolbar,
:width => RedmineCkeditorSetting.width,
:height => RedmineCkeditorSetting.height
}))
end
def enabled?
Setting.text_formatting == "CKEditor"
end
def apply_patch
require 'redmine_ckeditor/application_helper_patch'
require 'redmine_ckeditor/queries_helper_patch'
require 'redmine_ckeditor/rich_files_helper_patch'
require 'redmine_ckeditor/journals_controller_patch'
require 'redmine_ckeditor/messages_controller_patch'
require 'redmine_ckeditor/mail_handler_patch'
end
end
end
require 'redmine_ckeditor/hooks/journal_listener'
require 'redmine_ckeditor/pdf_patch'
require 'redmine_ckeditor/tempfile_patch'

View file

@ -0,0 +1,22 @@
require_dependency 'application_helper'
module ApplicationHelper
def ckeditor_javascripts
root = RedmineCkeditor.assets_root
javascript_tag("CKEDITOR_BASEPATH = '#{root}/ckeditor/';") +
javascript_include_tag("application", :plugin => "redmine_ckeditor") +
javascript_tag(RedmineCkeditor.plugins.map {|name|
path = "#{root}/ckeditor-contrib/plugins/#{name}/"
"CKEDITOR.plugins.addExternal('#{name}', '#{path}/');"
}.join("\n"))
end
def format_activity_description_with_ckeditor(text)
if RedmineCkeditor.enabled?
simple_format(truncate(HTMLEntities.new.decode(strip_tags(text.to_s)), :length => 120))
else
format_activity_description_without_ckeditor(text)
end
end
alias_method_chain :format_activity_description, :ckeditor
end

View file

@ -0,0 +1,36 @@
module RedmineCkeditor::Hooks
class JournalListener < Redmine::Hook::ViewListener
def view_journals_notes_form_after_notes(context)
return unless RedmineCkeditor.enabled?
project = context[:project]
journal = context[:journal]
javascript_tag <<-EOT
(function() {
var note_id = "journal_#{journal.id}_notes";
CKEDITOR.replace(note_id, #{RedmineCkeditor.options(project).to_json});
var note = $("#" + note_id);
var save_button = note.parent().find(":submit");
var preview_button = save_button.next();
var cancel_button = preview_button.next().get(0);
save_button.click(function() {
var editor = CKEDITOR.instances[note_id];
note.val(editor.getData());
editor.destroy();
});
preview_button.hide();
var cancel = cancel_button.onclick;
cancel_button.onclick = function() {
CKEDITOR.instances[note_id].destroy();
cancel();
return false;
};
})();
EOT
end
end
end

View file

@ -0,0 +1,40 @@
require_dependency 'journals_controller'
module RedmineCkeditor
module JournalsControllerPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :new, :ckeditor
end
end
module InstanceMethods
def new_with_ckeditor
unless RedmineCkeditor.enabled?
new_without_ckeditor
return
end
@journal = Journal.visible.find(params[:journal_id]) if params[:journal_id]
if @journal
user = @journal.user
text = @journal.notes
else
user = @issue.author
text = @issue.description
end
@content = "<p>#{ll(I18n.locale, :text_user_wrote, user)}</p>"
@content << "<blockquote>#{text}</blockquote><p/>"
render "new_with_ckeditor"
rescue ActiveRecord::RecordNotFound
render_404
end
end
end
JournalsController.send(:include, JournalsControllerPatch)
end

View file

@ -0,0 +1,29 @@
require_dependency 'mail_handler'
module RedmineCkeditor
module MailHandlerPatch
extend ActiveSupport::Concern
include ActionView::Helpers::TextHelper
included do
unloadable
alias_method_chain :cleaned_up_text_body, :ckeditor
alias_method_chain :extract_keyword!, :ckeditor
end
def cleaned_up_text_body_with_ckeditor
if RedmineCkeditor.enabled?
simple_format(cleaned_up_text_body_without_ckeditor)
else
cleaned_up_text_body_without_ckeditor
end
end
def extract_keyword_with_ckeditor!(text, attr, format=nil)
text = cleaned_up_text_body_without_ckeditor if RedmineCkeditor.enabled?
extract_keyword_without_ckeditor!(text, attr, format)
end
end
MailHandler.send(:include, MailHandlerPatch)
end

View file

@ -0,0 +1,32 @@
require_dependency 'messages_controller'
module RedmineCkeditor
module MessagesControllerPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
unloadable
alias_method_chain :quote, :ckeditor
end
end
module InstanceMethods
def quote_with_ckeditor
unless RedmineCkeditor.enabled?
quote_without_ckeditor
return
end
@subject = @message.subject
@subject = "RE: #{@subject}" unless @subject.starts_with?('RE:')
@content = "<p>#{ll(I18n.locale, :text_user_wrote, @message.author)}</p>"
@content << "<blockquote>#{ActionView::Base.full_sanitizer.sanitize(@message.content.to_s)}</blockquote><p/>"
render "quote_with_ckeditor"
end
end
end
MessagesController.send(:include, MessagesControllerPatch)
end

View file

@ -0,0 +1,61 @@
require_dependency 'redmine/export/pdf'
module RedmineCkeditor
module PDFPatch
def self.included(base)
base.class_eval do
alias_method_chain :formatted_text, :ckeditor
alias_method_chain :get_image_filename, :ckeditor
alias_method_chain :RDMwriteHTMLCell, :ckeditor
end
end
def formatted_text_with_ckeditor(text)
html = formatted_text_without_ckeditor(text)
html = HTMLEntities.new.decode(html) if RedmineCkeditor.enabled?
html
end
def RDMwriteHTMLCell_with_ckeditor(w, h, x, y, txt='', attachments=[], border=0, ln=1, fill=0)
@tmp_images = []
RDMwriteHTMLCell_without_ckeditor(w, h, x, y, txt, attachments, border, ln, fill)
@tmp_images.each do |item|
#logger.info item
File.delete(item) if File.file?(item)
end
end
def get_image_filename_with_ckeditor(attrname)
img = nil
if attrname.sub!(/^data:([^\/]+)\/([^;]+);base64,/, '')
type = $2
data = attrname.dup
# for base64 encording image such as 'clipboard_image_paste' plugin
if @tmp_images.nil?
@tmp_images = []
end
tmp = Tempfile.new([@tmp_images.length.to_s, '.'+type])
img = tmp.path
tmp.close
@tmp_images << img
File.open(img, 'wb') do |f|
f.write(Base64.decode64(data))
end
else
# for CKEditor file uploader with 'rich' plugin
img = if attrname.include?("/rich/rich_files/rich_files/")
Rails.root.join("public#{URI.decode(attrname)}").to_s
else
get_image_filename_without_ckeditor(attrname)
end
end
img
end
end
Redmine::Export::PDF::ITCPDF.send(:include, PDFPatch)
end

View file

@ -0,0 +1,13 @@
require_dependency 'queries_helper'
module QueriesHelper
def csv_value_with_ckeditor(column, issue, value)
if RedmineCkeditor.enabled? && column.name == :description
text = Rails::Html::FullSanitizer.new.sanitize(value.to_s)
text.gsub(/(?:\r\n\t*)+/, "\r").gsub("&nbsp;", " ").strip
else
csv_value_without_ckeditor(column, issue, value)
end
end
alias_method_chain :csv_value, :ckeditor
end

View file

@ -0,0 +1,24 @@
require_dependency 'rich/files_helper'
module RedmineCkeditor
module RichFilesHelperPatch
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :thumb_for_file, :redmine
end
end
module InstanceMethods
def thumb_for_file_with_redmine(file)
Redmine::Utils.relative_url_root + if file.simplified_type == "image"
file.rich_file.url(:rich_thumb)
else
"/plugin_assets/redmine_ckeditor/images/document-thumb.png"
end
end
end
end
Rich::FilesHelper.send(:include, RichFilesHelperPatch)
end

View file

@ -0,0 +1,9 @@
class Tempfile
def initialize_with_binmode(basename, *rest)
initialize_without_binmode(basename, *rest).tap do |f|
f.binmode if basename == "raw-upload."
end
end
alias_method_chain :initialize, :binmode
end

View file

@ -0,0 +1,38 @@
module RedmineCkeditor::WikiFormatting
class Formatter
include Redmine::WikiFormatting::LinksHelper
def initialize(text)
@text = text
end
def to_html(&block)
preserved = []
text = @text.gsub(/<pre>\s*(.*?)\s*<\/pre>/m) {|m|
content = if matched = $1.match(/<code\s+class="(\w+)">[\r\n]*(.*?)[\r\n]*<\/code>/m)
lang, code = matched.captures
code = Redmine::SyntaxHighlighting.highlight_by_language(code, lang)
%Q[<pre>\n<code class="#{lang} syntaxhl">#{code}</code>\n</pre>]
else
m
end
preserved.push(content)
"____preserved_#{preserved.size}____"
}.gsub(/{{.*?}}/m) {|m|
preserved.push(m)
"____preserved_#{preserved.size}____"
}
auto_link!(text)
text = ActionView::Base.white_list_sanitizer.sanitize(text,
:tags => RedmineCkeditor.allowed_tags,
:attributes => RedmineCkeditor.allowed_attributes
)
preserved.each.with_index(1) {|content, i|
text.gsub!("____preserved_#{i}____", content)
}
text
end
end
end

View file

@ -0,0 +1,60 @@
module RedmineCkeditor::WikiFormatting
module Helper
def replace_editor_tag(field_id)
javascript_tag <<-EOT
$(document).ready(function() {
#{replace_editor_script(field_id)}
});
EOT
end
def replace_editor_script(field_id)
<<-EOT
(function() {
var id = '#{field_id}';
var textarea = document.getElementById(id);
if (!textarea) return;
var editor = CKEDITOR.replace(textarea, #{RedmineCkeditor.options(@project).to_json});
editor.on("change", function() { textarea.value = editor.getSnapshot(); });
})();
EOT
end
def overwrite_functions
javascript_tag <<-EOT
function showAndScrollTo(id, focus) {
var elem = $("#" + id);
elem.show();
if (focus != null && CKEDITOR.instances.hasOwnProperty(focus)) { CKEDITOR.instances[focus].focus(); }
$('html, body').animate({scrollTop: elem.offset().top}, 100);
}
function destroyEditor(id) {
if (CKEDITOR.instances[id]) CKEDITOR.instances[id].destroy();
}
EOT
end
def initial_setup
overwrite_functions
end
def wikitoolbar_for(field_id)
if params[:format] == "js"
javascript_tag(replace_editor_script(field_id))
else
ckeditor_javascripts +
stylesheet_link_tag('editor', :plugin => 'redmine_ckeditor') +
initial_setup + replace_editor_tag(field_id)
end
end
def initial_page_content(page)
"<h1>#{ERB::Util.html_escape page.pretty_title}</h1>"
end
def heads_for_wiki_formatter
end
end
end

View file

@ -0,0 +1,100 @@
namespace :redmine_ckeditor do
namespace :assets do
desc "copy assets"
task :copy => :environment do
env = Sprockets::Environment.new(RedmineCkeditor.root)
Rails.application.config.assets.paths.each do |path|
env.append_path(path)
end
env.append_path("app/assets/javascripts")
%w(application.js browser.js).each do |asset|
assets = env.find_asset(asset)
assets.write_to(RedmineCkeditor.root.join("assets/javascripts", asset))
end
ckeditor = RedmineCkeditor.root.join("assets/ckeditor")
rm_rf ckeditor
cp_r RedmineCkeditor.root.join("app/assets/javascripts/ckeditor-releases"), ckeditor
rm ckeditor.join(".git")
end
end
class Migration
FORMATS = %w[textile markdown html]
def initialize(projects, from, to)
@from = from
@to = to
@projects = projects
end
def start
[@from, @to].each do |format|
next if FORMATS.include?(format)
puts "#{format} format is not supported."
puts "Available formats: #{FORMATS.join(", ")}"
return
end
messages = [
"*** WARNING ***",
"It is strongly recommended to backup your database before migration, because it cannot be rolled back completely.",
"***************"
]
if @projects.empty?
@projects = Project.all
messages << "projects: ALL"
else
messages << "projects: #{@projects.pluck(:identifier).join(",")}"
end
messages << "migration: #{@from} to #{@to}"
messages.each {|message| puts message}
print "Do you want to continue? (type 'y' to continue): "
unless STDIN.gets.chomp == 'y'
puts "Cancelled"
return
end
ActiveRecord::Base.transaction do
@projects.each do |project|
puts "project #{project.name}"
project.update_column(:description, convert(project.description))
migrate(:issues, project.issues, :description)
migrate(:journals, Journal.where(:journalized_type => "Issue",
:journalized_id => project.issues), :notes)
migrate(:documents, project.documents, :description)
migrate(:messages, Message.where(:board_id => project.boards), :content)
migrate(:news, project.news, :description)
migrate(:comments, Comment.where(:commented_type => "News",
:commented_id => project.news), :comments)
migrate(:wiki, WikiContent.where(:page_id => project.wiki.pages), :text) if project.wiki
end
end
end
def migrate(type, records, column)
n = records.count
return if n == 0
records.each_with_index do |record, i|
print "\rMigrating #{type} ... (#{i}/#{n})"
record.update_column(column, convert(record.send(column)))
end
puts "\rMigrating #{type} ... done "
end
def convert(text)
text && PandocRuby.convert(text, from: @from, to: @to)
end
end
desc "Migrate text to html"
task :migrate => :environment do
projects = Project.where(:identifier => ENV['PROJECT'].to_s.split(","))
from = ENV['FROM'] || Setting.text_formatting
from = "html" if from == "CKEditor"
to = ENV['TO'] || "html"
Migration.new(projects, from, to).start
end
end