[#604] Evaluate Liquid before Textile-to-HTML transformation.
This changes how the liquid integration works. It now integrates the Textile conversion step. This was necessary because if you first convert the snippets inside of loops and conditionals from Textile to HTML, you loose some important context information which is required to e.g. build proper lists in textile. We expect the standard case that Liquid tags return Textile markup instead of HTML. Thus, we can convert the final textile markup to HTML as a very last step. To allow existing and new macros (or tags) to return HTML for advanced usage, we save their respective output into the context and put a placeholder string into the generated markup. After the transformation to HTML, we insert the previously generated HTML into the string using search+replace in lib/chili_project/liquid/template.rb. Tags have to be registered using :html => true for this special treatment.
This commit is contained in:
parent
72fa3ff920
commit
82432f3f99
|
@ -462,14 +462,14 @@ module ApplicationHelper
|
|||
only_path = options.delete(:only_path) == false ? false : true
|
||||
|
||||
begin
|
||||
text = Redmine::WikiFormatting.to_html(Setting.text_formatting, text, :object => obj, :attribute => attr) { |macro, args| exec_macro(macro, obj, args) }
|
||||
liquid_template = Liquid::Template.parse(text)
|
||||
liquid_template = ChiliProject::Liquid::Template.parse(text)
|
||||
liquid_variables = get_view_instance_variables_for_liquid
|
||||
liquid_variables.merge!({'current_user' => User.current})
|
||||
liquid_variables.merge!({'toc' => '{{toc}}'}) # Pass toc through to replace later
|
||||
liquid_variables.merge!(ChiliProject::Liquid::Variables.macro_backwards_compatibility)
|
||||
|
||||
# Pass :view in a register so this view (with helpers) can be used inside of a tag
|
||||
text = liquid_template.render(liquid_variables, :registers => {:view => self})
|
||||
text = liquid_template.render(liquid_variables, :registers => {:view => self, :object => obj, :attribute => attr})
|
||||
|
||||
# Add Liquid errors to the log
|
||||
if Rails.logger && Rails.logger.debug?
|
||||
|
@ -480,7 +480,6 @@ module ApplicationHelper
|
|||
end
|
||||
Rails.logger.debug msg
|
||||
end
|
||||
text
|
||||
rescue Liquid::SyntaxError
|
||||
# Skip Liquid if there is a syntax error
|
||||
end
|
||||
|
@ -962,7 +961,7 @@ module ApplicationHelper
|
|||
# Expands the current menu item using JavaScript based on the params
|
||||
def expand_current_menu
|
||||
current_menu_class =
|
||||
case
|
||||
case
|
||||
when params[:controller] == "timelog"
|
||||
"reports"
|
||||
when params[:controller] == 'projects' && params[:action] == 'changelog'
|
||||
|
@ -981,7 +980,7 @@ module ApplicationHelper
|
|||
params[:controller]
|
||||
end
|
||||
|
||||
|
||||
|
||||
javascript_tag("jQuery.menu_expand({ menuItem: '.#{current_menu_class}' });")
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
require 'chili_project/liquid/liquid_ext'
|
||||
require 'chili_project/liquid/tags'
|
||||
|
||||
module ChiliProject
|
||||
module Liquid
|
||||
end
|
||||
end
|
|
@ -0,0 +1,7 @@
|
|||
module ChiliProject
|
||||
module Liquid
|
||||
module LiquidExt
|
||||
::Liquid::Context.send(:include, Context)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
module ChiliProject
|
||||
module Liquid
|
||||
module LiquidExt
|
||||
module Context
|
||||
def self.included(base)
|
||||
base.send(:include, InstanceMethods)
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def html_result(html)
|
||||
key = nil
|
||||
while key.nil? || html_results.has_key?(key)
|
||||
random = ActiveSupport::SecureRandom.hex(10)
|
||||
# This string must be passed untouched through Liquid and textile
|
||||
# It mustn't be changed in any way by any rendering stage.
|
||||
key = "!!html_results.#{random}!!"
|
||||
end
|
||||
html_results[key] = html
|
||||
key
|
||||
end
|
||||
|
||||
def html_results
|
||||
registers[:html_results] ||= {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
module ChiliProject::Liquid
|
||||
module Tags
|
||||
class TagError < StandardError; end
|
||||
|
||||
def self.register_tag(name, klass, options={})
|
||||
if options[:html]
|
||||
html_class = Class.new do
|
||||
def render(context)
|
||||
result = @tag.render(context)
|
||||
context.html_result(result)
|
||||
end
|
||||
|
||||
def method_missing(*args, &block)
|
||||
@tag.send(*args, &block)
|
||||
end
|
||||
end
|
||||
html_class.send :define_method, :initialize do |*args|
|
||||
@tag = klass.new(*args)
|
||||
end
|
||||
::Liquid::Template.register_tag(name, html_class)
|
||||
else
|
||||
::Liquid::Template.register_tag(name, klass)
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: reimplement old macros as tags and register them here
|
||||
# child_pages
|
||||
# hello_world
|
||||
# include
|
||||
# macro_list
|
||||
end
|
||||
end
|
|
@ -0,0 +1,96 @@
|
|||
module ChiliProject
|
||||
module Liquid
|
||||
class Template < ::Liquid::Template
|
||||
# creates a new <tt>Template</tt> object from liquid source code
|
||||
def self.parse(source)
|
||||
template = self.new
|
||||
template.parse(source)
|
||||
template
|
||||
end
|
||||
|
||||
|
||||
def context_from_render_options(*args)
|
||||
# This method is pulled out straight from the original
|
||||
# Liquid::Template#render
|
||||
context = case args.first
|
||||
when ::Liquid::Context
|
||||
args.shift
|
||||
when Hash
|
||||
::Liquid::Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors)
|
||||
when nil
|
||||
::Liquid::Context.new(assigns, instance_assigns, registers, @rethrow_errors)
|
||||
else
|
||||
raise ArgumentError, "Expect Hash or Liquid::Context as parameter"
|
||||
end
|
||||
|
||||
case args.last
|
||||
when Hash
|
||||
options = args.pop
|
||||
|
||||
if options[:registers].is_a?(Hash)
|
||||
self.registers.merge!(options[:registers])
|
||||
end
|
||||
|
||||
if options[:filters]
|
||||
context.add_filters(options[:filters])
|
||||
end
|
||||
|
||||
when Module
|
||||
context.add_filters(args.pop)
|
||||
when Array
|
||||
context.add_filters(args.pop)
|
||||
end
|
||||
context
|
||||
end
|
||||
|
||||
# Render takes a hash with local variables.
|
||||
#
|
||||
# if you use the same filters over and over again consider registering them globally
|
||||
# with <tt>Template.register_filter</tt>
|
||||
#
|
||||
# Following options can be passed:
|
||||
#
|
||||
# * <tt>filters</tt> : array with local filters
|
||||
# * <tt>registers</tt> : hash with register variables. Those can be accessed from
|
||||
# filters and tags and might be useful to integrate liquid more with its host application
|
||||
#
|
||||
def render(*args)
|
||||
return '' if @root.nil?
|
||||
|
||||
context = context_from_render_options(*args)
|
||||
context.registers[:html_results] ||= {}
|
||||
|
||||
# ENTER THE RENDERING STAGE
|
||||
|
||||
# 1. Render the input as Liquid
|
||||
# Some tags might register final HTML output in this stage.
|
||||
begin
|
||||
# for performance reasons we get a array back here. join will make a string out of it
|
||||
result = @root.render(context)
|
||||
result.respond_to?(:join) ? result.join : result
|
||||
ensure
|
||||
@errors = context.errors
|
||||
end
|
||||
|
||||
# 2. Perform the Wiki markup transformation (e.g. Textile)
|
||||
obj = context.registers[:object]
|
||||
attr = context.registers[:attribute]
|
||||
result = Redmine::WikiFormatting.to_html(Setting.text_formatting, result, :object => obj, :attribute => attr)
|
||||
|
||||
# 3. Now finally, replace the captured raw HTML bits in the final content
|
||||
length = nil
|
||||
# replace HTML results until we can find no additional variables
|
||||
while length != context.registers[:html_results].length do
|
||||
length = context.registers[:html_results].length
|
||||
context.registers[:html_results].delete_if do |key, value|
|
||||
# We use the block variant to avoid the evaluation of escaped
|
||||
# characters in +value+ during substitution.
|
||||
result.sub!(key) { |match| value }
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue