diff --git a/.github/agents/plan.agent.md b/.github/agents/plan.agent.md index 4d7252c48c..f0dc4b66f3 100644 --- a/.github/agents/plan.agent.md +++ b/.github/agents/plan.agent.md @@ -11,6 +11,8 @@ tools: - search/searchResults - search/usages - vscode/vscodeAPI + - execute/runInTerminal + - read/terminalLastCommand --- # Plan Mode - Strategic Planning & Architecture Assistant diff --git a/.github/agents/tdd-green.agent.md b/.github/agents/tdd-green.agent.md index 5889370ad4..ae1360ca1e 100644 --- a/.github/agents/tdd-green.agent.md +++ b/.github/agents/tdd-green.agent.md @@ -1,7 +1,16 @@ --- description: 'Implement minimal code to satisfy GitHub issue requirements and make failing tests pass without over-engineering.' name: 'TDD Green Phase - Make Tests Pass Quickly' -tools: ['github', 'findTestFiles', 'edit/editFiles', 'runTests', 'runCommands', 'codebase', 'filesystem', 'search', 'problems', 'testFailure', 'terminalLastCommand'] +tools: ["vscode/*", "findTestFiles", "edit/editFiles", "edit/createFile", "edit/createDirectory", "execute/runInTerminal", "search/codebase", "filesystem", "search", "read/problems", "execute/testFailure", "read/terminalLastCommand"] +handoffs: + - label: Return to TDD RED Phase + agent: tdd-red + prompt: Now create more test specs. + send: true + - label: Start Refactor + agent: tdd-refactor + prompt: Now refactor this work. + send: true --- # TDD Green Phase - Make Tests Pass Quickly diff --git a/.github/agents/tdd-red.agent.md b/.github/agents/tdd-red.agent.md index 5c12967d2a..3cee52bf3b 100644 --- a/.github/agents/tdd-red.agent.md +++ b/.github/agents/tdd-red.agent.md @@ -1,7 +1,12 @@ --- description: "Guide test-first development by writing failing tests that describe desired behaviour from GitHub issue context before implementation exists." name: "TDD Red Phase - Write Failing Tests First" -tools: ["github", "findTestFiles", "edit/editFiles", "runTests", "runCommands", "codebase", "filesystem", "search", "problems", "testFailure", "terminalLastCommand"] +tools: ["vscode/*", "findTestFiles", "edit/editFiles", "edit/createFile", "edit/createDirectory", "execute/runInTerminal", "search/codebase", "filesystem", "search", "read/problems", "execute/testFailure", "read/terminalLastCommand"] +handoffs: + - label: Start Implementation + agent: tdd-green + prompt: Now implement the plan outlined above. + send: true --- # TDD Red Phase - Write Failing Tests First diff --git a/.github/agents/tdd-refactor.agent.md b/.github/agents/tdd-refactor.agent.md index 527bf476a8..ccce2ace59 100644 --- a/.github/agents/tdd-refactor.agent.md +++ b/.github/agents/tdd-refactor.agent.md @@ -1,7 +1,7 @@ --- description: "Improve code quality, apply security best practices, and enhance design whilst maintaining green tests and GitHub issue compliance." name: "TDD Refactor Phase - Improve Quality & Security" -tools: ["github", "findTestFiles", "edit/editFiles", "runTests", "runCommands", "codebase", "filesystem", "search", "problems", "testFailure", "terminalLastCommand"] +tools: ["vscode/*", "findTestFiles", "edit/editFiles", "edit/createFile", "edit/createDirectory", "execute/runInTerminal", "search/codebase", "filesystem", "search", "read/problems", "execute/testFailure", "read/terminalLastCommand"] --- # TDD Refactor Phase - Improve Quality & Security diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index f16c0f06f7..65f24687fc 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -37,6 +37,13 @@ For all Ruby on Rails code you write, follow these conventions: [Ruby on Rails Coding Standards](instructions/ruby-on-rails.instructions.md) +### HTML and CSS Conventions + +- Avoid adding HTML styles inline; use CSS classes instead. +- Use BEM (Block, Element, Modifier) naming conventions for CSS classes. +- If JavaScript is needed for UI behavior, preferably use appropriate postprocessors rather than inline scripts. +- If inline ` END_HTML .html_safe diff --git a/app/helpers/edit_fields/edit_form_field_helper.rb b/app/helpers/edit_fields/edit_form_field_helper.rb index cce0003e68..6b4567758f 100644 --- a/app/helpers/edit_fields/edit_form_field_helper.rb +++ b/app/helpers/edit_fields/edit_form_field_helper.rb @@ -148,15 +148,14 @@ def edit_form_field( if cw got ||= '' got = got.html_safe - got += <<~END_SCRIPT - - END_SCRIPT - .html_safe + END_JS + end end end diff --git a/app/helpers/handlebars_precompiler_helper.rb b/app/helpers/handlebars_precompiler_helper.rb new file mode 100644 index 0000000000..8abfc12e49 --- /dev/null +++ b/app/helpers/handlebars_precompiler_helper.rb @@ -0,0 +1,330 @@ +# frozen_string_literal: true + +# Helper module for server-side Handlebars template precompilation. +# Provides cache key generation, template preprocessing, and batch CLI compilation. +# +# Workflow: +# 1. View helpers call #write_handlebars_template to write preprocessed templates to temp files +# 2. After all templates written, #compile_handlebars_templates runs single CLI call per type +# 3. CLI output is post-processed to split into individual compiled JS files +# 4. Compiled files are served from public/handlebars-{env}/ directory +# +# Thread Safety: +# Each request uses a unique request ID for its temp subdirectory to prevent +# race conditions when multiple concurrent requests are compiling templates. +module HandlebarsPrecompilerHelper + extend ActiveSupport::Concern + + # JavaScript wrapper used by Handlebars CLI to register compiled templates + COMPILED_HEAD = <<~ENDJS + (function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; + ENDJS + + # Generate unique request ID for temp directory isolation. + # Uses Thread.current to ensure each request thread gets its own ID. + # @return [String] unique identifier for this request + def handlebars_request_id + @handlebars_request_id ||= SecureRandom.hex(8) + end + + # Get request-specific temp directory for templates. + # Each request gets isolated temp files to prevent race conditions. + # @param is_partial [Boolean] whether this is for partials + # @return [Pathname] path to request-specific temp directory + def handlebars_temp_dir(is_partial:) + base = is_partial ? HandlebarsPrecompiler::PARTIALS_TMP_DIR : HandlebarsPrecompiler::TEMPLATES_TMP_DIR + base.join(handlebars_request_id) + end + + # Get public directory for compiled templates or partials. + # @param is_partial [Boolean] whether this is for partials + # @return [Pathname] path to public directory for compiled files + def handlebars_public_dir(is_partial:) + is_partial ? HandlebarsPrecompiler::PARTIALS_PUBLIC_DIR : HandlebarsPrecompiler::TEMPLATES_PUBLIC_DIR + end + + # Get URL relative path for templates or partials. + # @param is_partial [Boolean] whether this is for partials + # @return [String] URL relative path + def handlebars_url_path(is_partial:) + subdir = is_partial ? 'partials' : 'templates' + "#{HandlebarsPrecompiler::URL_RELATIVE_PATH}#{subdir}/" + end + + # Generate a cache key common to all users. + # Uses server_cache_version and item_updates from dynamic definitions. + # @return [String] 13-character hex string (truncated SHA256) + def handlebars_cache_key + @handlebars_cache_key ||= begin + ver = Application.server_cache_version + items = handlebars_item_updates_key + Digest::SHA256.hexdigest("#{ver}-#{items}")[0..12] + end + end + + # Extract item_updates logic for cache key generation. + # @return [String] concatenated timestamps of dynamic definitions + def handlebars_item_updates_key + @handlebars_item_updates_key ||= begin + cs = [Admin::MessageTemplate, DynamicModel, ActivityLog, ExternalIdentifier, + Admin::ConfigLibrary, Admin::PageLayout, Admin::AppConfiguration] + cs.map { |c| c.reorder(updated_at: :desc).limit(1).pluck(:updated_at)&.first.to_i.to_s }.join('-') + end + end + + # Generate filename for compiled output. + # @param template_id [String] the template identifier + # @return [String] safe filename with cache key suffix + def handlebars_compiled_filename(template_id) + safe_id = template_id.to_s.gsub(/[^a-zA-Z0-9_-]/, '_') + "#{safe_id}-#{handlebars_cache_key}.js" + end + + # Preprocess handlebars source to convert shorthand syntax to CLI-compatible format. + # Ports the logic from _fpa.setup_template_source in JavaScript. + # @param source [String] the raw Handlebars template source + # @return [String] preprocessed source ready for CLI compilation + def preprocess_handlebars_source(source) + return '' if source.nil? + + result = source.dup + + # Handle embedded_report and glyphicon patterns + # {{embedded_report_name}} -> {{embedded_report 'name' true}} + %w[embedded_report glyphicon].each do |pre| + result.gsub!(/\{\{#{pre}_([a-zA-Z0-9_]+)\}\}/) do + "{{#{pre} '#{::Regexp.last_match(1)}' true}}" + end + end + + # Handle tag_format patterns (double colon syntax) + # {{tag::format::args}} -> {{tag_format tag 'format' 'args'}} + result.gsub!(/\{\{([a-zA-Z0-9_]+)::([0-9a-z:_.]+)\}\}/) do + tag = ::Regexp.last_match(1) + parts = ::Regexp.last_match(2).split('::').map { |p| "'#{p}'" }.join(' ') + "{{tag_format #{tag} #{parts}}}" + end + + result + end + + def read_handlebars_template(template_id, is_partial: false) + public_dir = handlebars_public_dir(is_partial:) + safe_id = template_id.to_s.gsub(/[^a-zA-Z0-9_-]/, '_') + effective_id = is_partial ? safe_id.sub(/-partial\z/, '') : safe_id + compiled_filename = handlebars_compiled_filename(effective_id) + compiled_file = public_dir.join(compiled_filename) + raise FphsException, "Compiled Handlebars template not found: #{compiled_file}" unless File.exist?(compiled_file) + + File.read(compiled_file) + end + + # Write a template to temp file for batch compilation. + # Template content is preprocessed before writing. + # Uses request-specific temp directory to prevent race conditions. + # @param template_id [String] the template identifier + # @param content [String, nil] template content (if nil, captures from block) + # @param is_partial [Boolean] whether this is a partial (default: false) + # @yield Block that returns template content if content is nil + # @return [String] relative URL path to the compiled file (for javascript_include_tag) + def write_handlebars_template(template_id, content = nil, is_partial: false, &) + dir = handlebars_temp_dir(is_partial:) + public_dir = handlebars_public_dir(is_partial:) + url_path = handlebars_url_path(is_partial:) + safe_id = template_id.to_s.gsub(/[^a-zA-Z0-9_-]/, '_') + + # For partials, strip the -partial suffix from both temp and compiled filenames + # The Handlebars CLI uses the filename as the partial registration name + # e.g., master_id_summary_result-partial -> master_id_summary_result + # This ensures {{>master_id_summary_result}} finds the correct partial + effective_id = is_partial ? safe_id.sub(/-partial\z/, '') : safe_id + temp_file = dir.join("#{effective_id}.handlebars") + + # Use the effective_id (without -partial suffix for partials) for the compiled filename + # Templates and partials are stored in separate subdirectories to avoid name collisions + compiled_filename = handlebars_compiled_filename(effective_id) + compiled_file = public_dir.join(compiled_filename) + relative_path = "#{url_path}#{compiled_filename}" + + # Skip if compiled file already exists (avoid recompilation) + return relative_path if File.exist?(compiled_file) + + # Skip if temp file already written (deduplication within same request) + return relative_path if File.exist?(temp_file) + + # Get content from block if not provided + content ||= capture(&) if block_given? + + # Use placeholder for empty/blank templates - they still need to be registered + # in Handlebars.templates even if they render nothing + content = ' ' if content.blank? + + # Ensure directory exists before writing + FileUtils.mkdir_p(dir) + + # Preprocess and write to temp file + processed = preprocess_handlebars_source(content) + File.write(temp_file, processed) + + relative_path + end + + def write_multiple_handlebars_templates(requested_handlebars_templates) + template_html = [] + handlebars_partial_ids = [] + handlebars_template_ids = [] + requested_handlebars_templates.each do |template_info| + id = template_info[:id] + is_partial = template_info[:is_partial] + compiled_file_path = template_info[:compiled_file_path] + template_html << read_handlebars_template(id, is_partial:) + if is_partial + handlebars_partial_ids << id + else + handlebars_template_ids << id + end + end + + u = current_user || current_admin + app_type_id = u&.app_type_id if u.respond_to? :app_type_id + req_digest = Digest::SHA256.hexdigest([handlebars_template_ids, handlebars_partial_ids].join(',')) + filename = "requested-templates-#{u&.id}-#{u&.current_sign_in_at.to_i}-#{app_type_id}-#{req_digest}.js" + dir = HandlebarsPrecompiler::MULTI_PUBLIC_DIR.join(filename) + + # Add initialization header to ensure Handlebars.partials exists before partials register themselves + # The CLI-compiled partials assume Handlebars.partials already exists + init_header = <<~JS + (function() { + Handlebars.partials = Handlebars.partials || {}; + Handlebars.templates = Handlebars.templates || {}; + })(); + JS + File.write(dir, (init_header + template_html.join("\n")).html_safe) + + url_path = HandlebarsPrecompiler::URL_RELATIVE_PATH + ["#{url_path}/multi/#{filename}", handlebars_template_ids, handlebars_partial_ids] + end + + # Compile all templates from temp directories in a single CLI call per type. + # The CLI output is post-processed to split into individual compiled JS files. + # @raise [RuntimeError] if compilation fails + def compile_handlebars_templates + [false, true].each do |is_partial| + compile_handlebars_templates_for_type(is_partial:) + end + end + + private + + # Compile templates or partials from their respective temp directory. + # Uses request-specific temp directory to prevent race conditions. + # @param is_partial [Boolean] true for partials, false for templates + def compile_handlebars_templates_for_type(is_partial:) + dir = handlebars_temp_dir(is_partial:) + + # Skip if request-specific directory doesn't exist or is empty + return unless Dir.exist?(dir) + + file_list = Dir.glob(dir.join('*.handlebars')) + return if file_list.empty? + + type_name = is_partial ? 'partials' : 'templates' + Rails.logger.info do + "Compiling Handlebars #{type_name}: #{file_list.size} files (request: #{handlebars_request_id})" + end + + # Compile to a request-specific temporary output file + temp_output = HandlebarsPrecompiler::TMP_DIR.join("compiled_#{type_name}_#{handlebars_request_id}.js") + + # Build CLI command for batch compilation + handlebars_cmd = HandlebarsPrecompiler::HANDLEBARS_CLI.split + handlebars_cmd += file_list + handlebars_cmd += ['-f', temp_output.to_s] + handlebars_cmd << '--partial' if is_partial + # NOTE: Minify and source maps not supported in directory mode + + begin + Utilities::ProcessPipes.pipe_in_out(nil, handlebars_cmd) + rescue FphsException, StandardError => e + # Save a copy of the files for debugging before they might be cleaned up + debug_dir = Rails.root.join('tmp', 'agent-tmp', 'failed-templates') + FileUtils.mkdir_p(debug_dir) + file_list.each do |f| + FileUtils.cp(f, debug_dir.join(File.basename(f))) + rescue StandardError + nil + end + Rails.logger.error "Saved failed templates to #{debug_dir}" + + # Log files for debugging + Rails.logger.error "Files that failed: #{file_list.map { |f| File.basename(f) }.join(', ')}" + file_list.each do |f| + content = begin + File.read(f) + rescue StandardError + 'UNREADABLE' + end + Rails.logger.error "#{File.basename(f)}: #{content.length} bytes, first 100 chars: #{content[0..99]}" + end + error_msg = "Handlebars batch compilation failed for #{type_name}: #{e.message}" + Rails.logger.error error_msg + raise error_msg + end + + # Split combined output into individual files + split_compiled_output(temp_output, is_partial:) + + # Clean up request-specific temp directory + cleanup_temp_files(dir, temp_output) + end + + # Split the combined CLI output into individual compiled JS files. + # @param output_file [Pathname] path to the combined output file + # @param is_partial [Boolean] whether these are partials + def split_compiled_output(output_file, is_partial:) + return unless File.exist?(output_file) + + full_content = File.read(output_file) + public_dir = handlebars_public_dir(is_partial:) + + # Remove the outer IIFE wrapper that CLI adds + # Header: (function() {\n var template = Handlebars.template, templates = ... + # Footer: })(); + full_content = full_content.sub(COMPILED_HEAD, '') + full_content = full_content.sub(/\}\)\(\);\s*\z/, '') + + # Split on template boundaries using regex + # Pattern: end of one template (});) followed by start of next (templates[... or Handlebars.partials[...) + template_parts = full_content.split(/(?<=\}\);)\s*(?=(?:templates|Handlebars\.partials)\[)/) + + # Ensure public directory exists + FileUtils.mkdir_p(public_dir) + + # Write each template to its own file in the appropriate subdirectory + template_parts.each do |part| + next if part.strip.empty? + + # Extract template name from the registration line + name_match = part.match(/^(?:Handlebars\.partials|templates)\['(.+?)'\]/) + next unless name_match + + template_name = name_match[1] + + # Wrap in IIFE and write to file + content = "#{COMPILED_HEAD}#{part}\n})();" + compiled_filename = handlebars_compiled_filename(template_name) + output_path = public_dir.join(compiled_filename) + File.write(output_path, content) + end + end + + # Clean up temp files after compilation. + # Removes the request-specific temp directory and its contents. + # @param dir [Pathname] request-specific temp directory to remove + # @param temp_output [Pathname] temp combined output file to remove + def cleanup_temp_files(dir, temp_output) + FileUtils.rm_rf(dir) + FileUtils.rm_f(temp_output) + end +end diff --git a/app/helpers/report_results/reports_common_result_cell.rb b/app/helpers/report_results/reports_common_result_cell.rb index 097d9a54c9..0414a11f07 100644 --- a/app/helpers/report_results/reports_common_result_cell.rb +++ b/app/helpers/report_results/reports_common_result_cell.rb @@ -315,22 +315,29 @@ def cell_content_for_iframe block_id = SecureRandom.hex(10) - html = <<~END_HTML + iframe_html = <<~END_HTML - - - END_HTML + END_JS + end - html.html_safe + (iframe_html + content_script + loader_script).html_safe end private diff --git a/app/views/activity_logs/_common_search_results_template.html.erb b/app/views/activity_logs/_common_search_results_template.html.erb index 8f5f12819b..38b219b366 100644 --- a/app/views/activity_logs/_common_search_results_template.html.erb +++ b/app/views/activity_logs/_common_search_results_template.html.erb @@ -103,7 +103,7 @@ %> <%= render partial: 'common_templates/search_results_template', locals: mapped_vars %> <% end %> - - - - +<% end %> <% # Template for page layout resource results. A plain list without activity log controls. %> - +<% end %> <%# Activity Log main results including controls %> - - +<% end %> <% end %> diff --git a/app/views/admin/common/_parsed_config_panel.html.erb b/app/views/admin/common/_parsed_config_panel.html.erb index 794e2d9ae6..9ac17f6c9f 100644 --- a/app/views/admin/common/_parsed_config_panel.html.erb +++ b/app/views/admin/common/_parsed_config_panel.html.erb @@ -18,34 +18,6 @@
- - <% else %> diff --git a/app/views/admin/common_templates/_def_details_field_configs.html.erb b/app/views/admin/common_templates/_def_details_field_configs.html.erb index 95787c9d38..bbd1e0fe87 100644 --- a/app/views/admin/common_templates/_def_details_field_configs.html.erb +++ b/app/views/admin/common_templates/_def_details_field_configs.html.erb @@ -84,7 +84,7 @@ if field_configs.present? - +<% end %> <% end %> \ No newline at end of file diff --git a/app/views/admin/common_templates/_def_version_head_identifiers.html.erb b/app/views/admin/common_templates/_def_version_head_identifiers.html.erb index b6e6708a34..de52412964 100644 --- a/app/views/admin/common_templates/_def_version_head_identifiers.html.erb +++ b/app/views/admin/common_templates/_def_version_head_identifiers.html.erb @@ -1,8 +1,8 @@<% lines.each_with_index do |line, index| %><%= sprintf("%4d", index + 1) %> <%= ERB::Util.html_escape(line) %> <% end %>
No version changes to display.
<% else %> <% @version_diffs.each_with_index do |diff_data, idx| %> -<%= list_item.send(found_options) %>
<%= list_item.send(found_options) %>
-
+
+
+
+
<%= @rails_log.to_s.sub(/^\s*\n/, '') %>
diff --git a/app/views/common_templates/_common_page_template_result.html.erb b/app/views/common_templates/_common_page_template_result.html.erb
index fe63df4028..7191035c0b 100644
--- a/app/views/common_templates/_common_page_template_result.html.erb
+++ b/app/views/common_templates/_common_page_template_result.html.erb
@@ -8,7 +8,7 @@
# Then it simply calls the following partial "common_page_template_result_inner" with appropriate parameters.
# Logging is included to help diagnose missing template configurations.
%>
-
+<% end %>
<%
# Partial that renders the full activity log result item for a standalone page.
@@ -30,7 +30,7 @@
# ability to show an edit form or create new activity log records. The attribute 'data-subscription'
# has been provided, as has a hidden edit button, although this functionality is currently untested.
%>
-
+<% end %>
diff --git a/app/views/common_templates/_common_parts.html.erb b/app/views/common_templates/_common_parts.html.erb
index 00395d475e..691c797cbb 100644
--- a/app/views/common_templates/_common_parts.html.erb
+++ b/app/views/common_templates/_common_parts.html.erb
@@ -4,16 +4,16 @@
<%= render partial: 'common_templates/common_template_references' %>
<%= render partial: 'common_templates/common_template_list' %>
-
+<% end %>
-
+<%= handlebars_template_tag('rank_button', css_class: 'hidden handlebars-partial') do %>
+{{#has 'rank'}}{{#if rank}}{{rank}} - {{rank_name}}{{else}}(no rank){{/if}}{{/has}}
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<%= handlebars_template_tag('field_result_class', css_class: 'hidden handlebars-partial') do %>list-group-item result-field-container {{hyphenate full_name}}-{{key}} {{#if no_caption_before}}force-no-caption-before{{else is key "in" caption_before_keys_without_keep_label}}has-caption-before{{/if}} {{#is key external_id_attr}}is_external_id_item{{/is}} {{#if (fpa_state_item 'template_config' (underscore name) vdef_version 'field_options' key 'no_downcase')}}fo-no-downcase{{/if}} {{#if (fpa_state_item 'template_config' (underscore (or name_with_option_type name)) vdef_version 'field_options' key 'view_original_case')}}fo-view-original-case{{/if}}<% end %>
-
+<% end %>
-
+<% end %>
-
+<%= handlebars_template_tag('activity_log_data_template_name_partial', css_class: 'hidden handlebars-partial') do %>activity-log--{{#if rec_type}}{{hyphenate full_name}}-{{pluralize rec_type}}{{else}}{{hyphenate (pluralize full_name)}}{{/if}}-main-result-template<% end %>
<%
# The show button appears at the top of a parent item, such as a player_contact with record type phone
%>
-
+<% end %>
diff --git a/app/views/common_templates/_common_template_list.html.erb b/app/views/common_templates/_common_template_list.html.erb
index 1d7c161753..1caf64ff4d 100644
--- a/app/views/common_templates/_common_template_list.html.erb
+++ b/app/views/common_templates/_common_template_list.html.erb
@@ -5,7 +5,7 @@
# found in `views/common_templates/_search_results_template.html.erb`
%>
-
+<% end %>
-
+<% end %>
diff --git a/app/views/common_templates/_common_template_references.html.erb b/app/views/common_templates/_common_template_references.html.erb
index 08c0623fcd..4bd21dad1a 100644
--- a/app/views/common_templates/_common_template_references.html.erb
+++ b/app/views/common_templates/_common_template_references.html.erb
@@ -1,7 +1,7 @@
<%
# Handle the display of referenced items in search results.
%>
-
+<% end %>
diff --git a/app/views/common_templates/_common_template_result.html.erb b/app/views/common_templates/_common_template_result.html.erb
index a7fc93b9c9..299d70d116 100644
--- a/app/views/common_templates/_common_template_result.html.erb
+++ b/app/views/common_templates/_common_template_result.html.erb
@@ -6,20 +6,20 @@
%>
<% # Produce HTML attributes to help identify the block of fields, applying tag substitutions to each value %>
-
+<%= handlebars_template_tag('custom_block_attrs_html', css_class: 'hidden handlebars-partial') do %>{{#each custom_block_attrs}}{{hyphenate @key}}={{{template this}}} {{/each}}<% end %>
<% # Get the result heading caption, applying tag substitutions %>
-
-
+<%= handlebars_template_tag('show_result_caption', css_class: 'hidden handlebars-partial') do %>{{#with result_data}}{{{run_template ../caption}}}{{/with}}<% end %>
+<%= handlebars_template_tag('show_result_caption_id_hyphenated', css_class: 'hidden handlebars-partial') do %>{{#with result_data}}{{{id_hyphenate (run_template ../caption)}}}{{/with}}<% end %>
<%
# Format extra CSS classes for the result item block.
# Adds in the `template_class` value set by the standard mapping.
# Will apply tag substitutions to the dynamic definition's `view_options.extra_class` value
%>
-
+<%= handlebars_template_tag('result_extra_class', css_class: 'hidden handlebars-partial') do %>{{#with result_data}}{{../template_class}} {{run_template ../extra_class}}{{/with}}<% end %>
-
+<% end %>
<%
# Partial that sets up the template config for the display of a full dynamic result item, such
@@ -62,7 +62,7 @@
# Then it simply calls the following partial "common_template_result_inner" with appropriate parameters.
# Logging is included to help diagnose missing template configurations.
%>
-
+<% end %>
<%
# Partial that renders the full dynamic result item, such
@@ -81,7 +81,7 @@
# which are actually built by the OptionConfigs::TemplateOptionMapping
# method for external identifiers, dynamic models and activity logs.
%>
-
+<% end %>
diff --git a/app/views/common_templates/_common_template_result_fields.html.erb b/app/views/common_templates/_common_template_result_fields.html.erb
index 02a9f7f076..1a70418d15 100644
--- a/app/views/common_templates/_common_template_result_fields.html.erb
+++ b/app/views/common_templates/_common_template_result_fields.html.erb
@@ -1,4 +1,4 @@
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
diff --git a/app/views/common_templates/_edit_form_filestore.erb b/app/views/common_templates/_edit_form_filestore.erb
index ddb66f8f63..6c1afbda75 100644
--- a/app/views/common_templates/_edit_form_filestore.erb
+++ b/app/views/common_templates/_edit_form_filestore.erb
@@ -16,13 +16,13 @@ if mr
to_record_editable: !!mr.to_record_editable
};
%>
-
+<% end %>
<% end %>
\ No newline at end of file
diff --git a/app/views/common_templates/_references_results.html.erb b/app/views/common_templates/_references_results.html.erb
index c061f99b5c..e1bbda5209 100644
--- a/app/views/common_templates/_references_results.html.erb
+++ b/app/views/common_templates/_references_results.html.erb
@@ -1,11 +1,11 @@
-
+<% end %>
-
+<% end %>
diff --git a/app/views/common_templates/_search_results_template.html.erb b/app/views/common_templates/_search_results_template.html.erb
index bca0950223..deb9f250cf 100644
--- a/app/views/common_templates/_search_results_template.html.erb
+++ b/app/views/common_templates/_search_results_template.html.erb
@@ -301,8 +301,7 @@
end
%>
-
+<% end %>
<%
@@ -427,30 +426,30 @@
} %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
<% end %>
\ No newline at end of file
diff --git a/app/views/common_templates/e_signature/_show_parts.html.erb b/app/views/common_templates/e_signature/_show_parts.html.erb
index b763ada38f..1abbec55ae 100644
--- a/app/views/common_templates/e_signature/_show_parts.html.erb
+++ b/app/views/common_templates/e_signature/_show_parts.html.erb
@@ -1,9 +1,9 @@
-
+<% end %>
diff --git a/app/views/common_templates/edit_fields/_name_is_e_signed_document.html.erb b/app/views/common_templates/edit_fields/_name_is_e_signed_document.html.erb
index 3893926a2e..db04a5e6ed 100644
--- a/app/views/common_templates/edit_fields/_name_is_e_signed_document.html.erb
+++ b/app/views/common_templates/edit_fields/_name_is_e_signed_document.html.erb
@@ -1,4 +1,4 @@
-
-
+ <% end %>
<%= render partial: 'layouts/bootstrap_modal' %>
<%= render partial: 'layouts/bootstrap_modal' %>
-
+ <% end %>
+<%= retrieve_requested_handlebars_templates __FILE__ %>
diff --git a/app/views/layouts/child_error_reporter.html.erb b/app/views/layouts/child_error_reporter.html.erb
index c47b99722d..015f05269a 100644
--- a/app/views/layouts/child_error_reporter.html.erb
+++ b/app/views/layouts/child_error_reporter.html.erb
@@ -1,4 +1,4 @@
-
\ No newline at end of file
+<% end %>
\ No newline at end of file
diff --git a/app/views/layouts/e_signature.html.erb b/app/views/layouts/e_signature.html.erb
index c018300af3..50f0314595 100644
--- a/app/views/layouts/e_signature.html.erb
+++ b/app/views/layouts/e_signature.html.erb
@@ -1,7 +1,7 @@
-
+ ") %>
diff --git a/app/views/layouts/nfs_store/filestore.html.erb b/app/views/layouts/nfs_store/filestore.html.erb
index 390bb527e7..b3a69aec24 100644
--- a/app/views/layouts/nfs_store/filestore.html.erb
+++ b/app/views/layouts/nfs_store/filestore.html.erb
@@ -5,7 +5,7 @@
<%= csrf_meta_tags %>
<%= stylesheet_link_tag 'application', media: 'all' %>
- <%= javascript_include_tag 'application' %>
+ <%= javascript_include_tag 'application', nonce: true %>
@@ -13,21 +13,21 @@
-
+<%= javascript_include_tag "/js/vendor/jquery.ui.widget.js", nonce: true %>
-
+<%= javascript_include_tag "https://blueimp.github.io/JavaScript-Load-Image/js/load-image.all.min.js", nonce: true %>
-
+<%= javascript_include_tag "https://blueimp.github.io/JavaScript-Canvas-to-Blob/js/canvas-to-blob.min.js", nonce: true %>
-
+<%= javascript_include_tag "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js", nonce: true %>
-
+<%= javascript_include_tag "/js/jquery.iframe-transport.js", nonce: true %>
-
+<%= javascript_include_tag "/js/jquery.fileupload.js", nonce: true %>
-
+<% end %>
diff --git a/app/views/layouts/public_application.html.erb b/app/views/layouts/public_application.html.erb
index 4508b23f9f..1b2eef61dd 100644
--- a/app/views/layouts/public_application.html.erb
+++ b/app/views/layouts/public_application.html.erb
@@ -5,7 +5,7 @@
<%= stylesheet_link_tag 'application', media: 'all' %>
- <%= javascript_include_tag 'application' %>
+ <%= javascript_include_tag 'application', nonce: true %>
<%= csrf_meta_tags %>
diff --git a/app/views/masters/_cache_search_results_template.html.erb b/app/views/masters/_cache_search_results_template.html.erb
index 0c4cf073f5..ab06e5393b 100644
--- a/app/views/masters/_cache_search_results_template.html.erb
+++ b/app/views/masters/_cache_search_results_template.html.erb
@@ -1,13 +1,10 @@
<%
part = nil
if Rails.configuration.action_controller.perform_caching == false
- # part, _ = render_and_compress_partial('masters/search_results_template')
- part = render partial: 'masters/search_results_template'
+ part = render(partial: 'masters/search_results_template')
else
part = Rails.cache.fetch partial_cache_key(:master__search_results_template) do
- render partial: 'masters/search_results_template'
- # part, comp = render_and_compress_partial('masters/search_results_template')
- # comp
+ render(partial: 'masters/search_results_template')
end
end
%>
diff --git a/app/views/masters/_master_panels.html.erb b/app/views/masters/_master_panels.html.erb
index 3569d15a18..2980ffffcc 100644
--- a/app/views/masters/_master_panels.html.erb
+++ b/app/views/masters/_master_panels.html.erb
@@ -14,13 +14,13 @@
sort_sublists = (details_view_options&.sort_sublists || {}).with_indifferent_access
%>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
diff --git a/app/views/masters/_modal_pi_search_results_template.html.erb b/app/views/masters/_modal_pi_search_results_template.html.erb
index 6a69b5428f..c0217ebf9b 100644
--- a/app/views/masters/_modal_pi_search_results_template.html.erb
+++ b/app/views/masters/_modal_pi_search_results_template.html.erb
@@ -6,7 +6,7 @@
no_subject_details_label = '' if no_subject_details_label == 'none'
%>
-
+<% end %>
diff --git a/app/views/masters/_search_form_advanced.html.erb b/app/views/masters/_search_form_advanced.html.erb
index bf52a0c1ea..2a797f6575 100644
--- a/app/views/masters/_search_form_advanced.html.erb
+++ b/app/views/masters/_search_form_advanced.html.erb
@@ -60,7 +60,7 @@
-
+
@@ -254,8 +254,8 @@
<% end %>
-
+<% end %>
diff --git a/app/views/masters/_search_form_simple.erb b/app/views/masters/_search_form_simple.erb
index 452b842a1b..8c746bfd49 100644
--- a/app/views/masters/_search_form_simple.erb
+++ b/app/views/masters/_search_form_simple.erb
@@ -39,7 +39,7 @@
-
+
@@ -51,11 +51,10 @@
<%end%>
-
+<% end %>
diff --git a/app/views/masters/_search_results_master_tabs.html.erb b/app/views/masters/_search_results_master_tabs.html.erb
index 391c411b4e..73a8391b5d 100644
--- a/app/views/masters/_search_results_master_tabs.html.erb
+++ b/app/views/masters/_search_results_master_tabs.html.erb
@@ -1,5 +1,5 @@
-
+<% end %>
diff --git a/app/views/masters/_search_results_master_tabs_default.html.erb b/app/views/masters/_search_results_master_tabs_default.html.erb
index bc3410706a..f01735aa4e 100644
--- a/app/views/masters/_search_results_master_tabs_default.html.erb
+++ b/app/views/masters/_search_results_master_tabs_default.html.erb
@@ -2,7 +2,7 @@
# The app configuration "open panels" sets if any of these are opened by default.
default_panels = app_config_items(:open_panels) %>
-
+<% end %>
diff --git a/app/views/masters/_search_results_parts.html.erb b/app/views/masters/_search_results_parts.html.erb
index 4c5cf4e797..206184659c 100644
--- a/app/views/masters/_search_results_parts.html.erb
+++ b/app/views/masters/_search_results_parts.html.erb
@@ -3,32 +3,31 @@
no_subject_details_label = app_config_text(:header_no_subject_details_label, '(no subject details)')
no_subject_details_label = '' if no_subject_details_label == 'none'
%>
-
+<% end %>
-
+<% end %>
-
+<%= handlebars_template_tag('empty-template') do %><% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
-
+<% end %>
diff --git a/app/views/masters/_search_results_template.html.erb b/app/views/masters/_search_results_template.html.erb
index 6ea94659f0..5049d7e21f 100644
--- a/app/views/masters/_search_results_template.html.erb
+++ b/app/views/masters/_search_results_template.html.erb
@@ -138,18 +138,17 @@
<%= render partial: 'common_templates/common_parts' %>
<%= render partial: 'item_flags/search_results_template' %>
<%= render partial: 'filestore/result_templates' if current_user.has_access_to?(:access, :table, :nfs_store__manage__containers) %>
-<%= render partial: 'secure_view/preview' if current_user.can?(:view_files_as_html) || current_user.can?(:view_files_as_image) %>
<%= render partial: 'filestore/common_template_view', locals: {} if current_user.has_access_to?(:access, :table, :nfs_store__manage__containers) %>
<%= render partial: 'common_templates/e_signature/show_parts', locals: {} %>
-
+<%= handlebars_template_tag('empty') do %><% end %>
<%
# Display the set of search results in the "master search" format
# Is rendered by the AJAX request made by `app/views/masters/search.html.erb`
%>
-
+<% end %>
-
+<% end %>
-
+<% end %>
<%
# Render the master record tabs and panels.
@@ -189,7 +188,7 @@
# trackers, details and external IDs are provided.
# Otherwise the panels defined in the page layout(s) are used.
%>
-
+<% end %>
+
+<%= retrieve_requested_handlebars_templates __FILE__ %>
\ No newline at end of file
diff --git a/app/views/page_layouts/index.html.erb b/app/views/page_layouts/index.html.erb
index 5e5af04574..4a12fb6d90 100644
--- a/app/views/page_layouts/index.html.erb
+++ b/app/views/page_layouts/index.html.erb
@@ -18,9 +18,9 @@
- <%= link_to list_item.panel_label, page_layout_path(list_item.panel_name) %>
+ <%= link_to list_item.panel_label, page_layout_path(list_item.panel_name) %>
- <%= list_item.description %>
+ <%= list_item.description %>
<% end %>
diff --git a/app/views/redcap/data_dictionaries/_tab_panels.html.erb b/app/views/redcap/data_dictionaries/_tab_panels.html.erb
index f91e6627a2..4ebc21f136 100644
--- a/app/views/redcap/data_dictionaries/_tab_panels.html.erb
+++ b/app/views/redcap/data_dictionaries/_tab_panels.html.erb
@@ -35,7 +35,7 @@
Metadata
<% if @rc_data_dictionary.captured_metadata.present?%>
diff --git a/app/views/redcap/project_admins/_files_block.html.erb b/app/views/redcap/project_admins/_files_block.html.erb
index 2ffe1f476b..2c0b76e73c 100644
--- a/app/views/redcap/project_admins/_files_block.html.erb
+++ b/app/views/redcap/project_admins/_files_block.html.erb
@@ -13,7 +13,7 @@
) %>
project to view the stored REDCap files
<% elsif object_instance&.file_store&.id %>
-
+<% end %>
<% else %>
File store not configured
<% end %>
\ No newline at end of file
diff --git a/app/views/redcap/project_admins/_metadata_block.html.erb b/app/views/redcap/project_admins/_metadata_block.html.erb
index 75e847ef35..0aa17671db 100644
--- a/app/views/redcap/project_admins/_metadata_block.html.erb
+++ b/app/views/redcap/project_admins/_metadata_block.html.erb
@@ -2,7 +2,7 @@
Metadata
<% if object_instance.captured_project_info.present?%>
diff --git a/app/views/reports/_criteria.html.erb b/app/views/reports/_criteria.html.erb
index fc3e5b1278..43b1cc3679 100644
--- a/app/views/reports/_criteria.html.erb
+++ b/app/views/reports/_criteria.html.erb
@@ -30,7 +30,7 @@
<%= render partial: 'reports/criteria/fields' %>
<%= render partial: 'reports/criteria/inline_search_button_block' %>