mirror of
https://github.com/discourse/discourse.git
synced 2024-11-23 02:19:27 +08:00
DEV: Cleanup legacy asset compilation gems and code (#19177)
We now use Ember CLI (core/plugins) and DiscourseJSProcessor (themes) for all Ember and template compilation. This commit removes the remnants of the legacy Sprockets-based Ember compilation system. Sprockets, and its DiscourseJSProcess-based Babel transformations, is still in use for a few assets. Ideally that will be removed/replaced in the near future.
This commit is contained in:
parent
174a8b431b
commit
84bec1cbae
7
Gemfile
7
Gemfile
|
@ -63,15 +63,8 @@ gem 'active_model_serializers', '~> 0.8.3'
|
||||||
|
|
||||||
gem 'http_accept_language', require: false
|
gem 'http_accept_language', require: false
|
||||||
|
|
||||||
# Ember related gems need to be pinned cause they control client side
|
|
||||||
# behavior, we will push these versions up when upgrading ember
|
|
||||||
gem 'discourse-ember-rails', '0.18.6', require: 'ember-rails'
|
|
||||||
gem 'discourse-ember-source', '~> 3.12.2'
|
|
||||||
gem 'ember-handlebars-template', '0.8.0'
|
|
||||||
gem 'discourse-fonts', require: 'discourse_fonts'
|
gem 'discourse-fonts', require: 'discourse_fonts'
|
||||||
|
|
||||||
gem 'barber'
|
|
||||||
|
|
||||||
gem 'message_bus'
|
gem 'message_bus'
|
||||||
|
|
||||||
gem 'rails_multisite'
|
gem 'rails_multisite'
|
||||||
|
|
25
Gemfile.lock
25
Gemfile.lock
|
@ -73,9 +73,6 @@ GEM
|
||||||
aws-sigv4 (~> 1.1)
|
aws-sigv4 (~> 1.1)
|
||||||
aws-sigv4 (1.5.0)
|
aws-sigv4 (1.5.0)
|
||||||
aws-eventstream (~> 1, >= 1.0.2)
|
aws-eventstream (~> 1, >= 1.0.2)
|
||||||
barber (0.12.2)
|
|
||||||
ember-source (>= 1.0, < 3.1)
|
|
||||||
execjs (>= 1.2, < 3)
|
|
||||||
better_errors (2.9.1)
|
better_errors (2.9.1)
|
||||||
coderay (>= 1.0.0)
|
coderay (>= 1.0.0)
|
||||||
erubi (>= 1.0.0)
|
erubi (>= 1.0.0)
|
||||||
|
@ -119,14 +116,6 @@ GEM
|
||||||
diff-lcs (1.5.0)
|
diff-lcs (1.5.0)
|
||||||
diffy (3.4.2)
|
diffy (3.4.2)
|
||||||
digest (3.1.0)
|
digest (3.1.0)
|
||||||
discourse-ember-rails (0.18.6)
|
|
||||||
active_model_serializers
|
|
||||||
ember-data-source (>= 1.0.0.beta.5)
|
|
||||||
ember-handlebars-template (>= 0.1.1, < 1.0)
|
|
||||||
ember-source (>= 1.1.0)
|
|
||||||
jquery-rails (>= 1.0.17)
|
|
||||||
railties (>= 3.1)
|
|
||||||
discourse-ember-source (3.12.2.3)
|
|
||||||
discourse-fonts (0.0.9)
|
discourse-fonts (0.0.9)
|
||||||
discourse-seed-fu (2.3.12)
|
discourse-seed-fu (2.3.12)
|
||||||
activerecord (>= 3.1)
|
activerecord (>= 3.1)
|
||||||
|
@ -138,12 +127,6 @@ GEM
|
||||||
ecma-re-validator (0.4.0)
|
ecma-re-validator (0.4.0)
|
||||||
regexp_parser (~> 2.2)
|
regexp_parser (~> 2.2)
|
||||||
email_reply_trimmer (0.1.13)
|
email_reply_trimmer (0.1.13)
|
||||||
ember-data-source (3.0.2)
|
|
||||||
ember-source (>= 2, < 3.0)
|
|
||||||
ember-handlebars-template (0.8.0)
|
|
||||||
barber (>= 0.11.0)
|
|
||||||
sprockets (>= 3.3, < 4.1)
|
|
||||||
ember-source (2.18.2)
|
|
||||||
erubi (1.11.0)
|
erubi (1.11.0)
|
||||||
excon (0.94.0)
|
excon (0.94.0)
|
||||||
execjs (2.8.1)
|
execjs (2.8.1)
|
||||||
|
@ -185,10 +168,6 @@ GEM
|
||||||
image_size (3.2.0)
|
image_size (3.2.0)
|
||||||
in_threads (1.6.0)
|
in_threads (1.6.0)
|
||||||
jmespath (1.6.1)
|
jmespath (1.6.1)
|
||||||
jquery-rails (4.5.1)
|
|
||||||
rails-dom-testing (>= 1, < 3)
|
|
||||||
railties (>= 4.2.0)
|
|
||||||
thor (>= 0.14, < 2.0)
|
|
||||||
json (2.6.2)
|
json (2.6.2)
|
||||||
json-schema (3.0.0)
|
json-schema (3.0.0)
|
||||||
addressable (>= 2.8)
|
addressable (>= 2.8)
|
||||||
|
@ -531,7 +510,6 @@ DEPENDENCIES
|
||||||
annotate
|
annotate
|
||||||
aws-sdk-s3
|
aws-sdk-s3
|
||||||
aws-sdk-sns
|
aws-sdk-sns
|
||||||
barber
|
|
||||||
better_errors
|
better_errors
|
||||||
binding_of_caller
|
binding_of_caller
|
||||||
bootsnap
|
bootsnap
|
||||||
|
@ -546,13 +524,10 @@ DEPENDENCIES
|
||||||
css_parser
|
css_parser
|
||||||
diffy
|
diffy
|
||||||
digest
|
digest
|
||||||
discourse-ember-rails (= 0.18.6)
|
|
||||||
discourse-ember-source (~> 3.12.2)
|
|
||||||
discourse-fonts
|
discourse-fonts
|
||||||
discourse-seed-fu
|
discourse-seed-fu
|
||||||
discourse_dev_assets
|
discourse_dev_assets
|
||||||
email_reply_trimmer
|
email_reply_trimmer
|
||||||
ember-handlebars-template (= 0.8.0)
|
|
||||||
excon
|
excon
|
||||||
execjs
|
execjs
|
||||||
fabrication
|
fabrication
|
||||||
|
|
|
@ -172,18 +172,6 @@ module Discourse
|
||||||
require 'middleware/discourse_public_exceptions'
|
require 'middleware/discourse_public_exceptions'
|
||||||
config.exceptions_app = Middleware::DiscoursePublicExceptions.new(Rails.public_path)
|
config.exceptions_app = Middleware::DiscoursePublicExceptions.new(Rails.public_path)
|
||||||
|
|
||||||
# Our templates shouldn't start with 'discourse/app/templates'
|
|
||||||
config.handlebars.templates_root = {
|
|
||||||
'discourse/app/templates' => '',
|
|
||||||
'admin/addon/templates' => 'admin/templates/',
|
|
||||||
'wizard/addon/templates' => 'wizard/templates/',
|
|
||||||
'select-kit/addon/templates' => 'select-kit/templates/'
|
|
||||||
}
|
|
||||||
|
|
||||||
config.handlebars.raw_template_namespace = "__DISCOURSE_RAW_TEMPLATES"
|
|
||||||
Sprockets.register_mime_type 'text/x-handlebars', extensions: ['.hbr']
|
|
||||||
Sprockets.register_transformer 'text/x-handlebars', 'application/javascript', Ember::Handlebars::Template
|
|
||||||
|
|
||||||
require 'discourse_js_processor'
|
require 'discourse_js_processor'
|
||||||
require 'discourse_sourcemapping_url_processor'
|
require 'discourse_sourcemapping_url_processor'
|
||||||
|
|
||||||
|
@ -211,11 +199,6 @@ module Discourse
|
||||||
# our setup does not use rack cache and instead defers to nginx
|
# our setup does not use rack cache and instead defers to nginx
|
||||||
config.action_dispatch.rack_cache = nil
|
config.action_dispatch.rack_cache = nil
|
||||||
|
|
||||||
# ember stuff only used for asset precompilation, production variant plays up
|
|
||||||
config.ember.variant = :development
|
|
||||||
config.ember.ember_location = "#{Rails.root}/vendor/assets/javascripts/production/ember.js"
|
|
||||||
config.ember.handlebars_location = "#{Rails.root}/vendor/assets/javascripts/handlebars.js"
|
|
||||||
|
|
||||||
require 'auth'
|
require 'auth'
|
||||||
|
|
||||||
if GlobalSetting.relative_url_root.present?
|
if GlobalSetting.relative_url_root.present?
|
||||||
|
|
|
@ -43,9 +43,6 @@ Discourse::Application.configure do
|
||||||
# Send deprecation notices to registered listeners
|
# Send deprecation notices to registered listeners
|
||||||
config.active_support.deprecation = :notify
|
config.active_support.deprecation = :notify
|
||||||
|
|
||||||
# this will cause all handlebars templates to be pre-compiles, making your page faster
|
|
||||||
config.handlebars.precompile = true
|
|
||||||
|
|
||||||
# this setting enables rack_cache so it caches various requests in redis
|
# this setting enables rack_cache so it caches various requests in redis
|
||||||
config.enable_rack_cache = true
|
config.enable_rack_cache = true
|
||||||
|
|
||||||
|
|
|
@ -40,8 +40,6 @@ Discourse::Application.configure do
|
||||||
config.active_record.migration_error = :page_load
|
config.active_record.migration_error = :page_load
|
||||||
config.watchable_dirs['lib'] = [:rb]
|
config.watchable_dirs['lib'] = [:rb]
|
||||||
|
|
||||||
config.handlebars.precompile = true
|
|
||||||
|
|
||||||
# we recommend you use mailhog https://github.com/mailhog/MailHog
|
# we recommend you use mailhog https://github.com/mailhog/MailHog
|
||||||
config.action_mailer.smtp_settings = { address: "localhost", port: 1025 }
|
config.action_mailer.smtp_settings = { address: "localhost", port: 1025 }
|
||||||
|
|
||||||
|
|
|
@ -52,9 +52,6 @@ Discourse::Application.configure do
|
||||||
# Send deprecation notices to registered listeners
|
# Send deprecation notices to registered listeners
|
||||||
config.active_support.deprecation = :notify
|
config.active_support.deprecation = :notify
|
||||||
|
|
||||||
# this will cause all handlebars templates to be pre-compiled, making your page faster
|
|
||||||
config.handlebars.precompile = true
|
|
||||||
|
|
||||||
# allows developers to use mini profiler
|
# allows developers to use mini profiler
|
||||||
config.load_mini_profiler = GlobalSetting.load_mini_profiler
|
config.load_mini_profiler = GlobalSetting.load_mini_profiler
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,6 @@ Discourse::Application.configure do
|
||||||
# Send deprecation notices to registered listeners
|
# Send deprecation notices to registered listeners
|
||||||
config.active_support.deprecation = :notify
|
config.active_support.deprecation = :notify
|
||||||
|
|
||||||
# precompile handlebar assets
|
|
||||||
config.handlebars.precompile = true
|
|
||||||
|
|
||||||
# allows users to use mini profiler
|
# allows users to use mini profiler
|
||||||
config.load_mini_profiler = false
|
config.load_mini_profiler = false
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,6 @@ Discourse::Application.configure do
|
||||||
|
|
||||||
# lower iteration count for test
|
# lower iteration count for test
|
||||||
config.pbkdf2_iterations = 10
|
config.pbkdf2_iterations = 10
|
||||||
config.ember.variant = :development
|
|
||||||
|
|
||||||
config.assets.compile = true
|
config.assets.compile = true
|
||||||
config.assets.digest = false
|
config.assets.digest = false
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Eventually we aim to move away from using Barber to precompile assets.
|
|
||||||
# These overrides unblock us moving to more recent ember versions in the meantime
|
|
||||||
|
|
||||||
module BarberEmberPrecompilerFreedomPatch
|
|
||||||
# Use the template compiler JS from node_modules
|
|
||||||
def ember_template_precompiler
|
|
||||||
@ember ||= File.new("app/assets/javascripts/node_modules/ember-source/dist/ember-template-compiler.js")
|
|
||||||
end
|
|
||||||
|
|
||||||
# Apply a couple of extra shims for more recent ember-template-compilers
|
|
||||||
def source_fixes
|
|
||||||
shims = super
|
|
||||||
|
|
||||||
shims << <<~JS
|
|
||||||
module = {exports:{}}
|
|
||||||
|
|
||||||
console = {
|
|
||||||
log: function(){},
|
|
||||||
warn: function(){},
|
|
||||||
error: function(){}
|
|
||||||
};
|
|
||||||
JS
|
|
||||||
|
|
||||||
shims
|
|
||||||
end
|
|
||||||
|
|
||||||
# Recent ember-template-compilers fail if `option` is null
|
|
||||||
def compile(template, options = nil)
|
|
||||||
options = {} if options.nil?
|
|
||||||
super(template, options)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Barber::Ember::Precompiler.prepend(BarberEmberPrecompilerFreedomPatch)
|
|
|
@ -1,15 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Ember::Handlebars::Template
|
|
||||||
|
|
||||||
# TODO: Remove this after we move to Ember CLI
|
|
||||||
def template_path(path, config)
|
|
||||||
root = config.templates_root
|
|
||||||
|
|
||||||
config.templates_root.each do |k, v|
|
|
||||||
path = path.sub(/#{Regexp.quote(k)}\//, v)
|
|
||||||
end
|
|
||||||
|
|
||||||
path.split('/').join(config.templates_path_separator)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,141 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# barber patches to re-route raw compilation via ember compat handlebars
|
|
||||||
|
|
||||||
class Barber::Precompiler
|
|
||||||
def sources
|
|
||||||
[File.open("#{Rails.root}/app/assets/javascripts/node_modules/handlebars/dist/handlebars.js"),
|
|
||||||
precompiler]
|
|
||||||
end
|
|
||||||
|
|
||||||
def precompiler
|
|
||||||
if !@precompiler
|
|
||||||
loader = File.read("#{Rails.root}/app/assets/javascripts/node_modules/loader.js/dist/loader/loader.js")
|
|
||||||
source = File.read("#{Rails.root}/app/assets/javascripts/discourse-common/addon/lib/raw-handlebars.js")
|
|
||||||
|
|
||||||
transpiled = DiscourseJsProcessor.transpile(source, "#{Rails.root}/app/assets/javascripts/", "discourse-common/lib/raw-handlebars")
|
|
||||||
|
|
||||||
@precompiler = StringIO.new <<~JS
|
|
||||||
let __RawHandlebars;
|
|
||||||
|
|
||||||
(function(){
|
|
||||||
#{loader}
|
|
||||||
define("handlebars", ["exports"], function(exports){ exports.default = Handlebars; })
|
|
||||||
#{transpiled}
|
|
||||||
__RawHandlebars = require("discourse-common/lib/raw-handlebars").default;
|
|
||||||
})()
|
|
||||||
|
|
||||||
Barber = {
|
|
||||||
precompile: function(string) {
|
|
||||||
return __RawHandlebars.precompile(string, false).toString();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
JS
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
@precompiler
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
module Discourse
|
|
||||||
module Ember
|
|
||||||
module Handlebars
|
|
||||||
module Helper
|
|
||||||
def precompile_handlebars(string, input = nil)
|
|
||||||
"requirejs('discourse-common/lib/raw-handlebars').template(#{Barber::Precompiler.compile(string)});"
|
|
||||||
end
|
|
||||||
|
|
||||||
def compile_handlebars(string, input = nil)
|
|
||||||
"requirejs('discourse-common/lib/raw-handlebars').compile(#{indent(string).inspect});"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
class Ember::Handlebars::Template
|
|
||||||
prepend Discourse::Ember::Handlebars::Helper
|
|
||||||
|
|
||||||
def path_for(module_name, config)
|
|
||||||
# We need this for backward-compatibility reasons.
|
|
||||||
# Plugins may not have an app subdirectory.
|
|
||||||
template_path(module_name, config).inspect.gsub('discourse/templates/', '')
|
|
||||||
end
|
|
||||||
|
|
||||||
def global_template_target(namespace, module_name, config)
|
|
||||||
"#{namespace}[#{path_for(module_name, config)}]"
|
|
||||||
end
|
|
||||||
|
|
||||||
def call(input)
|
|
||||||
data = input[:data]
|
|
||||||
filename = input[:filename]
|
|
||||||
|
|
||||||
raw = handlebars?(filename)
|
|
||||||
|
|
||||||
if raw
|
|
||||||
template = data
|
|
||||||
else
|
|
||||||
template = mustache_to_handlebars(filename, data)
|
|
||||||
end
|
|
||||||
|
|
||||||
template_name = input[:name]
|
|
||||||
|
|
||||||
module_name =
|
|
||||||
case config.output_type
|
|
||||||
when :amd
|
|
||||||
amd_template_target(config.amd_namespace, template_name)
|
|
||||||
when :global
|
|
||||||
template_path(template_name, config)
|
|
||||||
else
|
|
||||||
raise "Unsupported `output_type`: #{config.output_type}"
|
|
||||||
end
|
|
||||||
|
|
||||||
meta = meta_supported? ? { moduleName: module_name } : false
|
|
||||||
|
|
||||||
if config.precompile
|
|
||||||
if raw
|
|
||||||
template = precompile_handlebars(template, input)
|
|
||||||
else
|
|
||||||
template = precompile_ember_handlebars(template, config.ember_template, input, meta)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if raw
|
|
||||||
template = compile_handlebars(data)
|
|
||||||
else
|
|
||||||
template = compile_ember_handlebars(template, config.ember_template, meta)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
case config.output_type
|
|
||||||
when :amd
|
|
||||||
"define('#{module_name}', ['exports'], function(__exports__){ __exports__['default'] = #{template} });"
|
|
||||||
when :global
|
|
||||||
if raw
|
|
||||||
return <<~JS
|
|
||||||
var __t = #{template};
|
|
||||||
requirejs('discourse-common/lib/raw-templates').addRawTemplate(#{path_for(template_name, config)}, __t);
|
|
||||||
JS
|
|
||||||
end
|
|
||||||
|
|
||||||
target = global_template_target('Ember.TEMPLATES', template_name, config)
|
|
||||||
"#{target} = #{template}\n"
|
|
||||||
else
|
|
||||||
raise "Unsupported `output_type`: #{config.output_type}"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# FIXME: Previously, ember-handlebars-templates uses the logical path which incorrectly
|
|
||||||
# returned paths with the `.raw` extension and our code is depending on the `.raw`
|
|
||||||
# to find the right template to use.
|
|
||||||
def actual_name(input)
|
|
||||||
actual_name = input[:name]
|
|
||||||
input[:filename].include?('.raw') ? "#{actual_name}.raw" : actual_name
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def handlebars?(filename)
|
|
||||||
filename.to_s =~ /\.raw\.(handlebars|hjs|hbs)/ || filename.to_s.ends_with?(".hbr") || filename.to_s.ends_with?(".hbr.erb")
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,26 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
ctx = MiniRacer::Context.new(timeout: 15000)
|
|
||||||
ctx.eval("var self = this; #{File.read("#{Rails.root}/vendor/assets/javascripts/babel.js")}")
|
|
||||||
ctx.eval(File.read(Ember::Source.bundled_path_for('ember-template-compiler.js')))
|
|
||||||
ctx.eval("module = {}; exports = {};")
|
|
||||||
ctx.attach("rails.logger.info", proc { |err| puts(">> #{err.to_s}") })
|
|
||||||
ctx.attach("rails.logger.error", proc { |err| puts(">> #{err.to_s}") })
|
|
||||||
ctx.eval <<JS
|
|
||||||
console = {
|
|
||||||
prefix: "",
|
|
||||||
log: function(msg){ rails.logger.info(console.prefix + msg); },
|
|
||||||
error: function(msg){ rails.logger.error(console.prefix + msg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
JS
|
|
||||||
source = File.read("#{Rails.root}/lib/javascripts/widget-hbs-compiler.js")
|
|
||||||
js_source = ::JSON.generate(source, quirks_mode: true)
|
|
||||||
js = ctx.eval("Babel.transform(#{js_source}, { ast: false, plugins: ['check-es2015-constants', 'transform-es2015-arrow-functions', 'transform-es2015-block-scoped-functions', 'transform-es2015-block-scoping', 'transform-es2015-classes', 'transform-es2015-computed-properties', 'transform-es2015-destructuring', 'transform-es2015-duplicate-keys', 'transform-es2015-for-of', 'transform-es2015-function-name', 'transform-es2015-literals', 'transform-es2015-object-super', 'transform-es2015-parameters', 'transform-es2015-shorthand-properties', 'transform-es2015-spread', 'transform-es2015-sticky-regex', 'transform-es2015-template-literals', 'transform-es2015-typeof-symbol', 'transform-es2015-unicode-regex'] }).code")
|
|
||||||
ctx.eval(js)
|
|
||||||
|
|
||||||
if ARGV[0].present?
|
|
||||||
source = File.read(ARGV[0])
|
|
||||||
js_source = ::JSON.generate(source, quirks_mode: true)
|
|
||||||
puts ctx.eval("exports.compile(#{js_source})")
|
|
||||||
end
|
|
|
@ -1,31 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
template = <<~HBS
|
|
||||||
{{attach widget="wat" attrs=(hash test="abc" text=(i18n "hello" count=attrs.wat))}}
|
|
||||||
{{action-link action="undo" className="undo" text=(i18n (concat "post.actions.undo." attrs.action))}}
|
|
||||||
{{actions-summary-item attrs=as}}
|
|
||||||
{{attach widget="actions-summary-item" attrs=as}}
|
|
||||||
{{testing value="hello"}}
|
|
||||||
{{date "today"}}
|
|
||||||
HBS
|
|
||||||
|
|
||||||
ctx = MiniRacer::Context.new(timeout: 15000)
|
|
||||||
ctx.eval("var self = this; #{File.read("#{Rails.root}/vendor/assets/javascripts/babel.js")}")
|
|
||||||
ctx.eval(File.read(Ember::Source.bundled_path_for('ember-template-compiler.js')))
|
|
||||||
ctx.eval("module = {}; exports = {};")
|
|
||||||
ctx.attach("rails.logger.info", proc { |err| puts(err.to_s) })
|
|
||||||
ctx.attach("rails.logger.error", proc { |err| puts(err.to_s) })
|
|
||||||
ctx.eval <<JS
|
|
||||||
console = {
|
|
||||||
prefix: "",
|
|
||||||
log: function(msg){ rails.logger.info(console.prefix + msg); },
|
|
||||||
error: function(msg){ rails.logger.error(console.prefix + msg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
JS
|
|
||||||
source = File.read("#{Rails.root}/lib/javascripts/widget-hbs-compiler.js")
|
|
||||||
ctx.eval(source)
|
|
||||||
|
|
||||||
js_source = ::JSON.generate(template, quirks_mode: true)
|
|
||||||
|
|
||||||
puts ctx.eval("exports.compile(#{js_source})")
|
|
Loading…
Reference in New Issue
Block a user