mirror of
https://github.com/discourse/discourse.git
synced 2025-06-15 08:41:21 +08:00
FEATURE: detect theme errors and catch them (#7589)
* FEATURE: detect theme errors and catch them * Bump COMPILER_VERSION * Feedback * Override eslint no console for one line * Can't use our ajax method * remove emoji from translation file
This commit is contained in:
@ -660,5 +660,39 @@ export function postRNWebviewMessage(prop, value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function reportToLogster(name, error) {
|
||||||
|
const data = {
|
||||||
|
message: `${name} theme/component is throwing errors`,
|
||||||
|
stacktrace: error.stack
|
||||||
|
};
|
||||||
|
|
||||||
|
Ember.$.ajax(`${Discourse.BaseUri}/logs/report_js_error`, {
|
||||||
|
data,
|
||||||
|
type: "POST",
|
||||||
|
cache: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// this function is used in lib/theme_javascript_compiler.rb
|
||||||
|
export function rescueThemeError(name, error, api) {
|
||||||
|
/* eslint-disable-next-line no-console */
|
||||||
|
console.error(`"${name}" error:`, error);
|
||||||
|
reportToLogster(name, error);
|
||||||
|
|
||||||
|
const currentUser = api.getCurrentUser();
|
||||||
|
if (!currentUser || !currentUser.admin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = `${Discourse.BaseUri}/admin/customize/themes`;
|
||||||
|
const message = I18n.t("themes.broken_theme_alert", {
|
||||||
|
theme: name,
|
||||||
|
path: `<a href="${path}">${path}</a>`
|
||||||
|
});
|
||||||
|
const alertDiv = document.createElement("div");
|
||||||
|
alertDiv.classList.add("broken-theme-alert");
|
||||||
|
alertDiv.innerHTML = `⚠️ ${message}`;
|
||||||
|
document.body.prepend(alertDiv);
|
||||||
|
}
|
||||||
|
|
||||||
// This prevents a mini racer crash
|
// This prevents a mini racer crash
|
||||||
export default {};
|
export default {};
|
||||||
|
@ -732,3 +732,13 @@ table {
|
|||||||
color: $danger;
|
color: $danger;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.broken-theme-alert {
|
||||||
|
font-size: $base-font-size;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 5px 0;
|
||||||
|
background: $danger-medium;
|
||||||
|
text-align: center;
|
||||||
|
z-index: z("max");
|
||||||
|
color: $secondary;
|
||||||
|
}
|
||||||
|
@ -53,11 +53,19 @@ class Theme < ActiveRecord::Base
|
|||||||
|
|
||||||
Theme.expire_site_cache! if saved_change_to_user_selectable? || saved_change_to_name?
|
Theme.expire_site_cache! if saved_change_to_user_selectable? || saved_change_to_name?
|
||||||
notify_with_scheme = saved_change_to_color_scheme_id?
|
notify_with_scheme = saved_change_to_color_scheme_id?
|
||||||
|
name_changed = saved_change_to_name?
|
||||||
|
|
||||||
reload
|
reload
|
||||||
settings_field&.ensure_baked! # Other fields require setting to be **baked**
|
settings_field&.ensure_baked! # Other fields require setting to be **baked**
|
||||||
theme_fields.each(&:ensure_baked!)
|
theme_fields.each(&:ensure_baked!)
|
||||||
|
|
||||||
|
if name_changed
|
||||||
|
theme_fields.select { |f| f.basic_html_field? }.each do |f|
|
||||||
|
f.value_baked = nil
|
||||||
|
f.ensure_baked!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
remove_from_cache!
|
remove_from_cache!
|
||||||
clear_cached_settings!
|
clear_cached_settings!
|
||||||
ColorScheme.hex_cache.clear
|
ColorScheme.hex_cache.clear
|
||||||
|
@ -63,7 +63,7 @@ class ThemeField < ActiveRecord::Base
|
|||||||
validates :name, format: { with: /\A[a-z_][a-z0-9_-]*\z/i },
|
validates :name, format: { with: /\A[a-z_][a-z0-9_-]*\z/i },
|
||||||
if: Proc.new { |field| ThemeField.theme_var_type_ids.include?(field.type_id) }
|
if: Proc.new { |field| ThemeField.theme_var_type_ids.include?(field.type_id) }
|
||||||
|
|
||||||
COMPILER_VERSION = 10
|
COMPILER_VERSION = 11
|
||||||
|
|
||||||
belongs_to :theme
|
belongs_to :theme
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ class ThemeField < ActiveRecord::Base
|
|||||||
errors = []
|
errors = []
|
||||||
javascript_cache || build_javascript_cache
|
javascript_cache || build_javascript_cache
|
||||||
|
|
||||||
js_compiler = ThemeJavascriptCompiler.new(theme_id)
|
js_compiler = ThemeJavascriptCompiler.new(theme_id, self.theme.name)
|
||||||
|
|
||||||
doc = Nokogiri::HTML.fragment(html)
|
doc = Nokogiri::HTML.fragment(html)
|
||||||
|
|
||||||
@ -153,7 +153,7 @@ class ThemeField < ActiveRecord::Base
|
|||||||
def process_translation
|
def process_translation
|
||||||
errors = []
|
errors = []
|
||||||
javascript_cache || build_javascript_cache
|
javascript_cache || build_javascript_cache
|
||||||
js_compiler = ThemeJavascriptCompiler.new(theme_id)
|
js_compiler = ThemeJavascriptCompiler.new(theme_id, self.theme.name)
|
||||||
begin
|
begin
|
||||||
data = translation_data
|
data = translation_data
|
||||||
|
|
||||||
|
@ -187,6 +187,7 @@ en:
|
|||||||
|
|
||||||
themes:
|
themes:
|
||||||
default_description: "Default"
|
default_description: "Default"
|
||||||
|
broken_theme_alert: "Theme/component %{theme} has errors which may prevent your site from working correctly! You can disable it at %{path}."
|
||||||
|
|
||||||
s3:
|
s3:
|
||||||
regions:
|
regions:
|
||||||
|
@ -147,9 +147,10 @@ class ThemeJavascriptCompiler
|
|||||||
|
|
||||||
attr_accessor :content
|
attr_accessor :content
|
||||||
|
|
||||||
def initialize(theme_id)
|
def initialize(theme_id, theme_name)
|
||||||
@theme_id = theme_id
|
@theme_id = theme_id
|
||||||
@content = +""
|
@content = +""
|
||||||
|
@theme_name = theme_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def prepend_settings(settings_hash)
|
def prepend_settings(settings_hash)
|
||||||
@ -212,12 +213,18 @@ class ThemeJavascriptCompiler
|
|||||||
wrapped = <<~PLUGIN_API_JS
|
wrapped = <<~PLUGIN_API_JS
|
||||||
(function() {
|
(function() {
|
||||||
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
||||||
|
const __theme_name__ = "#{@theme_name.gsub('"', "\\\"")}";
|
||||||
const settings = Discourse.__container__
|
const settings = Discourse.__container__
|
||||||
.lookup("service:theme-settings")
|
.lookup("service:theme-settings")
|
||||||
.getObjectForTheme(#{@theme_id});
|
.getObjectForTheme(#{@theme_id});
|
||||||
const themePrefix = (key) => `theme_translations.#{@theme_id}.${key}`;
|
const themePrefix = (key) => `theme_translations.#{@theme_id}.${key}`;
|
||||||
Discourse._registerPluginCode('#{version}', api => {
|
Discourse._registerPluginCode('#{version}', api => {
|
||||||
|
try {
|
||||||
#{es6_source}
|
#{es6_source}
|
||||||
|
} catch(err) {
|
||||||
|
const rescue = require("discourse/lib/utilities").rescueThemeError;
|
||||||
|
rescue(__theme_name__, err, api);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -109,6 +109,24 @@ describe Theme do
|
|||||||
expect(Theme.lookup_field(theme.id, :desktop, "head_tag")).to eq("<b>I am bold</b>")
|
expect(Theme.lookup_field(theme.id, :desktop, "head_tag")).to eq("<b>I am bold</b>")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "changing theme name should re-transpile HTML theme fields" do
|
||||||
|
theme.update!(name: "old_name")
|
||||||
|
html = <<~HTML
|
||||||
|
<script type='text/discourse-plugin' version='0.1'>
|
||||||
|
const x = 1;
|
||||||
|
</script>
|
||||||
|
HTML
|
||||||
|
theme.set_field(target: :common, name: "head_tag", value: html)
|
||||||
|
theme.save!
|
||||||
|
field = theme.theme_fields.where(value: html).first
|
||||||
|
old_value = field.value_baked
|
||||||
|
|
||||||
|
theme.update!(name: "new_name")
|
||||||
|
field.reload
|
||||||
|
new_value = field.value_baked
|
||||||
|
expect(old_value).not_to eq(new_value)
|
||||||
|
end
|
||||||
|
|
||||||
it 'should precompile fragments in body and head tags' do
|
it 'should precompile fragments in body and head tags' do
|
||||||
with_template = <<HTML
|
with_template = <<HTML
|
||||||
<script type='text/x-handlebars' name='template'>
|
<script type='text/x-handlebars' name='template'>
|
||||||
@ -329,6 +347,7 @@ HTML
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "allows values to be used in JS" do
|
it "allows values to be used in JS" do
|
||||||
|
theme.name = 'awesome theme"'
|
||||||
theme.set_field(target: :settings, name: :yaml, value: "name: bob")
|
theme.set_field(target: :settings, name: :yaml, value: "name: bob")
|
||||||
theme_field = theme.set_field(target: :common, name: :after_header, value: '<script type="text/discourse-plugin" version="1.0">alert(settings.name); let a = ()=>{};</script>')
|
theme_field = theme.set_field(target: :common, name: :after_header, value: '<script type="text/discourse-plugin" version="1.0">alert(settings.name); let a = ()=>{};</script>')
|
||||||
theme.save!
|
theme.save!
|
||||||
@ -343,12 +362,18 @@ HTML
|
|||||||
})();
|
})();
|
||||||
(function () {
|
(function () {
|
||||||
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
||||||
|
var __theme_name__ = "awesome theme\\\"";
|
||||||
var settings = Discourse.__container__.lookup("service:theme-settings").getObjectForTheme(#{theme.id});
|
var settings = Discourse.__container__.lookup("service:theme-settings").getObjectForTheme(#{theme.id});
|
||||||
var themePrefix = function themePrefix(key) {
|
var themePrefix = function themePrefix(key) {
|
||||||
return 'theme_translations.#{theme.id}.' + key;
|
return 'theme_translations.#{theme.id}.' + key;
|
||||||
};
|
};
|
||||||
Discourse._registerPluginCode('1.0', function (api) {
|
Discourse._registerPluginCode('1.0', function (api) {
|
||||||
|
try {
|
||||||
alert(settings.name);var a = function a() {};
|
alert(settings.name);var a = function a() {};
|
||||||
|
} catch (err) {
|
||||||
|
var rescue = require("discourse/lib/utilities").rescueThemeError;
|
||||||
|
rescue(__theme_name__, err, api);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@ -372,12 +397,18 @@ HTML
|
|||||||
})();
|
})();
|
||||||
(function () {
|
(function () {
|
||||||
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
if ('Discourse' in window && typeof Discourse._registerPluginCode === 'function') {
|
||||||
|
var __theme_name__ = "awesome theme\\\"";
|
||||||
var settings = Discourse.__container__.lookup("service:theme-settings").getObjectForTheme(#{theme.id});
|
var settings = Discourse.__container__.lookup("service:theme-settings").getObjectForTheme(#{theme.id});
|
||||||
var themePrefix = function themePrefix(key) {
|
var themePrefix = function themePrefix(key) {
|
||||||
return 'theme_translations.#{theme.id}.' + key;
|
return 'theme_translations.#{theme.id}.' + key;
|
||||||
};
|
};
|
||||||
Discourse._registerPluginCode('1.0', function (api) {
|
Discourse._registerPluginCode('1.0', function (api) {
|
||||||
|
try {
|
||||||
alert(settings.name);var a = function a() {};
|
alert(settings.name);var a = function a() {};
|
||||||
|
} catch (err) {
|
||||||
|
var rescue = require("discourse/lib/utilities").rescueThemeError;
|
||||||
|
rescue(__theme_name__, err, api);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
Reference in New Issue
Block a user