diff --git a/app/assets/javascripts/admin/components/themes-list.js.es6 b/app/assets/javascripts/admin/components/themes-list.js.es6
index 00b7af61547..56006005886 100644
--- a/app/assets/javascripts/admin/components/themes-list.js.es6
+++ b/app/assets/javascripts/admin/components/themes-list.js.es6
@@ -31,7 +31,7 @@ export default Ember.Component.extend({
)
inactiveThemes(themes) {
if (this.get("componentsTabActive")) {
- return themes.filter(theme => theme.get("parentThemes.length") <= 0);
+ return themes.filter(theme => theme.get("parent_themes.length") <= 0);
}
return themes.filter(
theme => !theme.get("user_selectable") && !theme.get("default")
@@ -46,7 +46,7 @@ export default Ember.Component.extend({
)
activeThemes(themes) {
if (this.get("componentsTabActive")) {
- return themes.filter(theme => theme.get("parentThemes.length") > 0);
+ return themes.filter(theme => theme.get("parent_themes.length") > 0);
} else {
themes = themes.filter(
theme => theme.get("user_selectable") || theme.get("default")
diff --git a/app/assets/javascripts/admin/templates/customize-themes-show.hbs b/app/assets/javascripts/admin/templates/customize-themes-show.hbs
index 5ca9c901c29..10cfc8fe3ef 100644
--- a/app/assets/javascripts/admin/templates/customize-themes-show.hbs
+++ b/app/assets/javascripts/admin/templates/customize-themes-show.hbs
@@ -16,33 +16,91 @@
{{/each}}
- {{#if model.remote_theme}}
- {{#if model.remote_theme.remote_url}}
- {{model.remote_theme.remote_url}}
- {{/if}}
- {{i18n "admin.customize.theme.about_theme"}}
- {{#if model.remote_theme.license_url}}
- {{i18n "admin.customize.theme.license"}} {{d-icon "copyright"}}
- {{/if}}
- {{/if}}
-
- {{#if model.parentThemes}}
-
-
{{i18n "admin.customize.theme.component_of"}}
-
- {{#each model.parentThemes as |theme|}}
- - {{#link-to 'adminCustomizeThemes.show' theme replace=true}}{{theme.name}}{{/link-to}}
- {{/each}}
-
+ {{#unless model.enabled}}
+
+ {{i18n "admin.customize.theme.required_version.error"}}
+ {{#if model.remote_theme.minimum_discourse_version}}
+ {{i18n "admin.customize.theme.required_version.minimum" version=model.remote_theme.minimum_discourse_version}}
+ {{/if}}
+ {{#if model.remote_theme.maximum_discourse_version}}
+ {{i18n "admin.customize.theme.required_version.minimum" version=model.remote_theme.maximum_discourse_version}}
+ {{/if}}
- {{/if}}
+ {{/unless}}
{{#unless model.component}}
{{inline-edit-checkbox action=(action "applyDefault") labelKey="admin.customize.theme.is_default" checked=model.default}}
{{inline-edit-checkbox action=(action "applyUserSelectable") labelKey="admin.customize.theme.user_selectable" checked=model.user_selectable}}
+ {{/unless}}
+ {{#if model.remote_theme}}
+
+ {{#if model.remote_theme.remote_url}}
+
{{i18n "admin.customize.theme.source_url"}} {{d-icon "link"}}
+ {{/if}}
+ {{#if model.remote_theme.about_url}}
+
{{i18n "admin.customize.theme.about_theme"}} {{d-icon "link"}}
+ {{/if}}
+ {{#if model.remote_theme.license_url}}
+
{{i18n "admin.customize.theme.license"}} {{d-icon "link"}}
+ {{/if}}
+
+ {{#if model.description}}
+
{{model.description}}
+ {{/if}}
+
+
+ {{#if model.remote_theme.authors}}{{i18n "admin.customize.theme.authors"}} {{model.remote_theme.authors}}{{/if}}
+ {{#if model.remote_theme.theme_version}}{{i18n "admin.customize.theme.version"}} {{model.remote_theme.theme_version}}{{/if}}
+
+
+
+ {{#if model.remote_theme.is_git}}
+
+ {{#if showRemoteError}}
+
+ {{d-icon "exclamation-triangle"}} {{I18n "admin.customize.theme.repo_unreachable"}}
+
+
+ {{model.remoteError}}
+
+ {{/if}}
+
+ {{#if model.remote_theme.commits_behind}}
+ {{#d-button action=(action "updateToLatest") icon="download" class='btn-primary'}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}}
+ {{else}}
+ {{#d-button action=(action "checkForThemeUpdates") icon="refresh" class="btn-default"}}{{i18n "admin.customize.theme.check_for_updates"}}{{/d-button}}
+ {{/if}}
+
+
+ {{#if updatingRemote}}
+ {{i18n 'admin.customize.theme.updating'}}
+ {{else}}
+ {{#if model.remote_theme.commits_behind}}
+ {{i18n 'admin.customize.theme.commits_behind' count=model.remote_theme.commits_behind}}
+ {{#if model.remote_theme.github_diff_link}}
+
+ {{i18n 'admin.customize.theme.compare_commits'}}
+
+ {{/if}}
+ {{else}}
+ {{#unless showRemoteError}}
+ {{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}}
+ {{/unless}}
+ {{/if}}
+ {{/if}}
+
+ {{else}}
+
+ {{d-icon "info-circle"}} {{i18n "admin.customize.theme.imported_from_archive"}}
+
+ {{/if}}
+
+ {{/if}}
+
+ {{#unless model.component}}
{{i18n "admin.customize.theme.color_scheme"}}
{{i18n "admin.customize.theme.color_scheme_select"}}
@@ -60,6 +118,17 @@
{{/unless}}
+ {{#if parentThemes}}
+
+
{{i18n "admin.customize.theme.component_of"}}
+
+ {{#each parentThemes as |theme|}}
+ - {{#link-to 'adminCustomizeThemes.show' theme replace=true}}{{theme.name}}{{/link-to}}
+ {{/each}}
+
+
+ {{/if}}
+
{{i18n "admin.customize.theme.css_html"}}
{{#if model.hasEditedFields}}
@@ -75,47 +144,7 @@
{{/if}}
- {{#if model.remote_theme.is_git}}
- {{#if model.remote_theme.commits_behind}}
- {{#d-button action=(action "updateToLatest") icon="download" class='btn-primary'}}{{i18n "admin.customize.theme.update_to_latest"}}{{/d-button}}
- {{else}}
- {{#d-button action=(action "checkForThemeUpdates") icon="refresh" class="btn-default"}}{{i18n "admin.customize.theme.check_for_updates"}}{{/d-button}}
- {{/if}}
- {{/if}}
-
{{#d-button action=(action "editTheme") class="btn btn-default edit"}}{{i18n 'admin.customize.theme.edit_css_html'}}{{/d-button}}
- {{#if model.remote_theme.is_git}}
-
- {{#if updatingRemote}}
- {{i18n 'admin.customize.theme.updating'}}
- {{else}}
- {{#if model.remote_theme.commits_behind}}
- {{i18n 'admin.customize.theme.commits_behind' count=model.remote_theme.commits_behind}}
- {{#if model.remote_theme.github_diff_link}}
-
- {{i18n 'admin.customize.theme.compare_commits'}}
-
- {{/if}}
- {{else}}
- {{#unless showRemoteError}}
- {{i18n 'admin.customize.theme.up_to_date'}} {{format-date model.remote_theme.updated_at leaveAgo="true"}}
- {{/unless}}
- {{/if}}
- {{/if}}
-
- {{#if showRemoteError}}
-
- {{d-icon "exclamation-triangle"}} {{I18n "admin.customize.theme.repo_unreachable"}}
-
-
- {{model.remoteError}}
-
- {{/if}}
- {{else if model.remote_theme}}
-
- {{d-icon "info-circle"}} {{i18n "admin.customize.theme.imported_from_archive"}}
-
- {{/if}}
diff --git a/app/assets/stylesheets/common/admin/customize.scss b/app/assets/stylesheets/common/admin/customize.scss
index 0c233ff7810..4f3d151a973 100644
--- a/app/assets/stylesheets/common/admin/customize.scss
+++ b/app/assets/stylesheets/common/admin/customize.scss
@@ -137,26 +137,35 @@
display: inline-block;
vertical-align: top;
- .url {
- margin-bottom: 10px;
- }
-
.title {
font-size: $font-up-4;
font-weight: bold;
margin-bottom: 10px;
}
+
+ .theme-description {
+ display: block;
+ margin: 10px 0;
+ }
+
+ .metadata {
+ .authors,
+ .version {
+ display: block;
+
+ .heading {
+ font-weight: bold;
+ }
+ }
+ }
+
.remote-url,
.about-url,
.license-url {
- display: block;
- margin-bottom: 10px;
- }
- .remote-url {
- margin-top: -5px;
- font-size: $font-down-1;
- font-style: italic;
+ display: inline-block;
+ margin-right: 10px;
}
+
.mini-title {
font-size: $font-up-1;
font-weight: bold;
@@ -347,6 +356,7 @@
}
.setting-label {
width: 25%;
+ word-wrap: break-word;
h3 {
margin-top: 0;
margin-bottom: 0.5rem;
diff --git a/app/models/remote_theme.rb b/app/models/remote_theme.rb
index 0f1a28af62c..0e89c160dac 100644
--- a/app/models/remote_theme.rb
+++ b/app/models/remote_theme.rb
@@ -3,6 +3,14 @@ require_dependency 'theme_store/tgz_importer'
require_dependency 'upload_creator'
class RemoteTheme < ActiveRecord::Base
+ METADATA_PROPERTIES = %i{
+ license_url
+ about_url
+ authors
+ theme_version
+ minimum_discourse_version
+ maximum_discourse_version
+ }
class ImportError < StandardError; end
@@ -16,6 +24,8 @@ class RemoteTheme < ActiveRecord::Base
joins("JOIN themes ON themes.remote_theme_id = remote_themes.id").where.not(remote_url: "")
}
+ validates_format_of :minimum_discourse_version, :maximum_discourse_version, with: Discourse::VERSION_REGEXP, allow_nil: true
+
def self.extract_theme_info(importer)
JSON.parse(importer["about.json"])
rescue TypeError, JSON::ParserError
@@ -123,8 +133,12 @@ class RemoteTheme < ActiveRecord::Base
end
end
- self.license_url = theme_info["license_url"]
- self.about_url = theme_info["about_url"]
+ METADATA_PROPERTIES.each do |property|
+ self.public_send(:"#{property}=", theme_info[property.to_s])
+ end
+ if !self.valid?
+ raise ImportError, I18n.t("themes.import_error.about_json_values", errors: self.errors.full_messages.join(","))
+ end
importer.all_files.each do |filename|
next unless opts = ThemeField.opts_from_file_path(filename)
diff --git a/app/models/theme.rb b/app/models/theme.rb
index 45cbcb6882d..df91e6e8f32 100644
--- a/app/models/theme.rb
+++ b/app/models/theme.rb
@@ -7,7 +7,6 @@ require_dependency 'theme_translation_parser'
require_dependency 'theme_translation_manager'
class Theme < ActiveRecord::Base
-
# TODO: remove in 2019
self.ignored_columns = ["key"]
@@ -23,7 +22,7 @@ class Theme < ActiveRecord::Base
has_many :child_themes, -> { order(:name) }, through: :child_theme_relation, source: :child_theme
has_many :parent_themes, -> { order(:name) }, through: :parent_theme_relation, source: :parent_theme
has_many :color_schemes
- belongs_to :remote_theme
+ belongs_to :remote_theme, autosave: true
has_one :settings_field, -> { where(target_id: Theme.targets[:settings], name: "yaml") }, class_name: 'ThemeField'
@@ -53,9 +52,6 @@ class Theme < ActiveRecord::Base
Theme.expire_site_cache! if saved_change_to_user_selectable? || saved_change_to_name?
- @dependant_themes = nil
- @included_themes = nil
-
remove_from_cache!
clear_cached_settings!
ColorScheme.hex_cache.clear
@@ -125,16 +121,24 @@ class Theme < ActiveRecord::Base
end
def self.transform_ids(ids, extend: true)
- return [] if ids.blank?
+ get_set_cache "#{extend ? "extended_" : ""}transformed_ids_#{ids.join("_")}" do
+ next [] if ids.blank?
- ids.uniq!
- parent = ids.first
+ ids = ids.dup
+ ids.uniq!
+ parent = ids.shift
- components = ids[1..-1]
- components.push(*components_for(parent)) if extend
- components.sort!.uniq!
+ components = ids
+ components.push(*components_for(parent)) if extend
+ components.sort!.uniq!
- [parent, *components]
+ all_ids = [parent, *components]
+
+ enabled_ids = Theme.where(id: all_ids).includes(:remote_theme)
+ .select(&:enabled?).pluck(:id)
+
+ all_ids & enabled_ids # Maintain ordering using intersection
+ end
end
def set_default!
@@ -151,6 +155,18 @@ class Theme < ActiveRecord::Base
SiteSetting.default_theme_id == id
end
+ def enabled?
+ if minimum_version = remote_theme&.minimum_discourse_version
+ return false unless Discourse.has_needed_version?(Discourse::VERSION::STRING, minimum_version)
+ end
+
+ if maximum_version = remote_theme&.maximum_discourse_version
+ return false unless Discourse.has_needed_version?(maximum_version, Discourse::VERSION::STRING)
+ end
+
+ true
+ end
+
def component_validations
return unless component
@@ -234,7 +250,7 @@ class Theme < ActiveRecord::Base
end
def notify_theme_change(with_scheme: false)
- theme_ids = (dependant_themes&.pluck(:id) || []).unshift(self.id)
+ theme_ids = Theme.transform_ids([id])
self.class.notify_theme_change(theme_ids, with_scheme: with_scheme)
end
@@ -244,30 +260,6 @@ class Theme < ActiveRecord::Base
end
end
- def dependant_themes
- @dependant_themes ||= resolve_dependant_themes(:up)
- end
-
- def included_themes
- @included_themes ||= resolve_dependant_themes(:down)
- end
-
- def resolve_dependant_themes(direction)
- if direction == :up
- join_field = "parent_theme_id"
- where_field = "child_theme_id"
- elsif direction == :down
- join_field = "child_theme_id"
- where_field = "parent_theme_id"
- else
- raise "Unknown direction"
- end
-
- return [] unless id
-
- Theme.joins("JOIN child_themes ON themes.id = child_themes.#{join_field}").where("#{where_field} = ?", id)
- end
-
def self.resolve_baked_field(theme_ids, target, name)
list_baked_fields(theme_ids, target, name).map { |f| f.value_baked || f.value }.join("\n")
end
@@ -293,7 +285,7 @@ class Theme < ActiveRecord::Base
end
def list_baked_fields(target, name)
- theme_ids = (included_themes&.pluck(:id) || []).unshift(self.id)
+ theme_ids = Theme.transform_ids([id])
self.class.list_baked_fields(theme_ids, target, name)
end
@@ -338,7 +330,7 @@ class Theme < ActiveRecord::Base
def all_theme_variables
fields = {}
- ids = (included_themes&.pluck(:id) || []).unshift(self.id)
+ ids = Theme.transform_ids([id])
ThemeField.find_by_theme_ids(ids).where(type_id: ThemeField.theme_var_type_ids).each do |field|
next if fields.key?(field.name)
fields[field.name] = field
@@ -349,18 +341,22 @@ class Theme < ActiveRecord::Base
def add_child_theme!(theme)
new_relation = child_theme_relation.new(child_theme_id: theme.id)
if new_relation.save
- @included_themes = nil
child_themes.reload
save!
+ Theme.clear_cache!
else
raise Discourse::InvalidParameters.new(new_relation.errors.full_messages.join(", "))
end
end
- def translations
+ def internal_translations
+ translations(internal: true)
+ end
+
+ def translations(internal: false)
fallbacks = I18n.fallbacks[I18n.locale]
begin
- data = theme_fields.find_first_locale_fields([id], fallbacks).first&.translation_data(with_overrides: false)
+ data = theme_fields.find_first_locale_fields([id], fallbacks).first&.translation_data(with_overrides: false, internal: internal)
return {} if data.nil?
best_translations = {}
fallbacks.reverse.each do |locale|
@@ -400,7 +396,7 @@ class Theme < ActiveRecord::Base
def included_settings
hash = {}
- self.included_themes.each do |theme|
+ Theme.where(id: Theme.transform_ids([id])).each do |theme|
hash.merge!(theme.cached_settings)
end
@@ -435,17 +431,21 @@ class Theme < ActiveRecord::Base
end
def generate_metadata_hash
- {
- name: name,
- about_url: remote_theme&.about_url,
- license_url: remote_theme&.license_url,
- component: component,
- assets: {}.tap do |hash|
+ {}.tap do |meta|
+ meta[:name] = name
+ meta[:component] = component
+
+ RemoteTheme::METADATA_PROPERTIES.each do |property|
+ meta[property] = remote_theme&.public_send(property)
+ end
+
+ meta[:assets] = {}.tap do |hash|
theme_fields.where(type_id: ThemeField.types[:theme_upload_var]).each do |field|
hash[field.name] = "assets/#{field.upload.original_filename}"
end
- end,
- color_schemes: {}.tap do |hash|
+ end
+
+ meta[:color_schemes] = {}.tap do |hash|
schemes = self.color_schemes
# The selected color scheme may not belong to the theme, so include it anyway
schemes = [self.color_scheme] + schemes if self.color_scheme
@@ -453,7 +453,8 @@ class Theme < ActiveRecord::Base
hash[scheme.name] = {}.tap { |colors| scheme.colors.each { |color| colors[color.name] = color.hex } }
end
end
- }
+
+ end
end
end
diff --git a/app/models/theme_field.rb b/app/models/theme_field.rb
index d7cd020b08f..34c24f1f496 100644
--- a/app/models/theme_field.rb
+++ b/app/models/theme_field.rb
@@ -119,17 +119,17 @@ class ThemeField < ActiveRecord::Base
[doc.to_s, errors&.join("\n")]
end
- def raw_translation_data
+ def raw_translation_data(internal: false)
# Might raise ThemeTranslationParser::InvalidYaml
- ThemeTranslationParser.new(self).load
+ ThemeTranslationParser.new(self, internal: internal).load
end
- def translation_data(with_overrides: true)
+ def translation_data(with_overrides: true, internal: false)
fallback_fields = theme.theme_fields.find_locale_fields([theme.id], I18n.fallbacks[name])
fallback_data = fallback_fields.each_with_index.map do |field, index|
begin
- field.raw_translation_data
+ field.raw_translation_data(internal: internal)
rescue ThemeTranslationParser::InvalidYaml
# If this is the locale with the error, raise it.
# If not, let the other theme_field raise the error when it processes itself
diff --git a/app/serializers/theme_serializer.rb b/app/serializers/theme_serializer.rb
index fbd9b8d6ffb..645285de28c 100644
--- a/app/serializers/theme_serializer.rb
+++ b/app/serializers/theme_serializer.rb
@@ -45,9 +45,9 @@ class BasicThemeSerializer < ApplicationSerializer
end
class RemoteThemeSerializer < ApplicationSerializer
- attributes :id, :remote_url, :remote_version, :local_version, :about_url,
- :license_url, :commits_behind, :remote_updated_at, :updated_at,
- :github_diff_link, :last_error_text, :is_git?
+ attributes :id, :remote_url, :remote_version, :local_version, :commits_behind,
+ :remote_updated_at, :updated_at, :github_diff_link, :last_error_text, :is_git?,
+ :license_url, :about_url, :authors, :theme_version, :minimum_discourse_version, :maximum_discourse_version
# wow, AMS has some pretty nutty logic where it tries to find the path here
# from action dispatch, tell it not to
@@ -61,7 +61,7 @@ class RemoteThemeSerializer < ApplicationSerializer
end
class ThemeSerializer < BasicThemeSerializer
- attributes :color_scheme, :color_scheme_id, :user_selectable, :remote_theme_id, :settings, :errors
+ attributes :color_scheme, :color_scheme_id, :user_selectable, :remote_theme_id, :settings, :errors, :enabled?, :description
has_one :user, serializer: UserNameSerializer, embed: :object
@@ -102,4 +102,8 @@ class ThemeSerializer < BasicThemeSerializer
def include_errors?
@errors.present?
end
+
+ def description
+ object.internal_translations.find { |t| t.key == "theme_metadata.description" } &.value
+ end
end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 3227ab57ccc..faea38838b6 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -3386,8 +3386,15 @@ en:
is_private: "Theme is in a private git repository"
remote_branch: "Branch name (optional)"
public_key: "Grant the following public key access to the repo:"
- about_theme: "About Theme"
+ about_theme: "About"
license: "License"
+ version: "Version:"
+ authors: "Authored by:"
+ source_url: "Source"
+ required_version:
+ error: "This theme has been automatically disabled because it is not compatible with this version of Discourse."
+ minimum: "Requires Discourse version {{version}} or above."
+ maximum: "Requires Discourse version {{version}} or below."
component_of: "Component of:"
update_to_latest: "Update to Latest"
check_for_updates: "Check for Updates"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index ce915400901..e8d7e0420d8 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -77,6 +77,7 @@ en:
import_error:
generic: An error occured while importing that theme
about_json: "Import Error: about.json does not exist, or is invalid"
+ about_json_values: "about.json contains invalid values: %{errors}"
git: "Error cloning git repository, access is denied or repository is not found"
unpack_failed: "Failed to unpack file"
errors:
diff --git a/db/migrate/20190122132732_add_fields_to_remote_themes.rb b/db/migrate/20190122132732_add_fields_to_remote_themes.rb
new file mode 100644
index 00000000000..8cd22f963a9
--- /dev/null
+++ b/db/migrate/20190122132732_add_fields_to_remote_themes.rb
@@ -0,0 +1,8 @@
+class AddFieldsToRemoteThemes < ActiveRecord::Migration[5.2]
+ def change
+ add_column :remote_themes, :authors, :string
+ add_column :remote_themes, :theme_version, :string
+ add_column :remote_themes, :minimum_discourse_version, :string
+ add_column :remote_themes, :maximum_discourse_version, :string
+ end
+end
diff --git a/lib/theme_translation_parser.rb b/lib/theme_translation_parser.rb
index 3ce75803331..616c395a928 100644
--- a/lib/theme_translation_parser.rb
+++ b/lib/theme_translation_parser.rb
@@ -1,8 +1,10 @@
class ThemeTranslationParser
+ INTERNAL_KEYS = [:theme_metadata]
class InvalidYaml < StandardError; end
- def initialize(setting_field)
+ def initialize(setting_field, internal: internal)
@setting_field = setting_field
+ @internal = internal
end
def self.check_contains_hashes(hash)
@@ -22,6 +24,9 @@ class ThemeTranslationParser
parsed.deep_symbolize_keys!
+ parsed[@setting_field.name.to_sym].slice!(*INTERNAL_KEYS) if @internal
+ parsed[@setting_field.name.to_sym].except!(*INTERNAL_KEYS) if !@internal
+
parsed
end
end
diff --git a/lib/version.rb b/lib/version.rb
index 73c53ea98c7..9238a0388de 100644
--- a/lib/version.rb
+++ b/lib/version.rb
@@ -1,4 +1,6 @@
module Discourse
+ VERSION_REGEXP = /\A\d+\.\d+\.\d+(\.beta\d+)?\z/ unless defined? ::Discourse::VERSION_REGEXP
+
# work around reloader
unless defined? ::Discourse::VERSION
module VERSION #:nodoc:
diff --git a/spec/components/theme_store/tgz_exporter_spec.rb b/spec/components/theme_store/tgz_exporter_spec.rb
index 46da6defc13..cde028d12dd 100644
--- a/spec/components/theme_store/tgz_exporter_spec.rb
+++ b/spec/components/theme_store/tgz_exporter_spec.rb
@@ -11,7 +11,9 @@ describe ThemeStore::TgzExporter do
image = file_from_fixtures("logo.png")
upload = UploadCreator.new(image, "logo.png").create_for(-1)
theme.set_field(target: :common, name: :logo, upload_id: upload.id, type: :theme_upload_var)
- theme.build_remote_theme(remote_url: "", about_url: "abouturl", license_url: "licenseurl")
+ theme.build_remote_theme(remote_url: "", about_url: "abouturl", license_url: "licenseurl",
+ authors: "David Taylor", theme_version: "1.0", minimum_discourse_version: "1.0.0",
+ maximum_discourse_version: "3.0.0.beta1")
cs1 = Fabricate(:color_scheme, name: 'Orphan Color Scheme', color_scheme_colors: [
Fabricate(:color_scheme_color, name: 'header_primary', hex: 'F0F0F0'),
@@ -71,6 +73,10 @@ describe ThemeStore::TgzExporter do
"assets": {
"logo": "assets/logo.png"
},
+ "authors": "David Taylor",
+ "minimum_discourse_version": "1.0.0",
+ "maximum_discourse_version": "3.0.0.beta1",
+ "theme_version": "1.0",
"color_schemes": {
"Orphan Color Scheme": {
"header_primary": "F0F0F0",
diff --git a/spec/models/remote_theme_spec.rb b/spec/models/remote_theme_spec.rb
index 490ac1694ea..1e9f23c98a4 100644
--- a/spec/models/remote_theme_spec.rb
+++ b/spec/models/remote_theme_spec.rb
@@ -24,6 +24,8 @@ describe RemoteTheme do
"name": "awesome theme",
"about_url": "#{about_url}",
"license_url": "https://www.site.com/license",
+ "theme_version": "1.0",
+ "minimum_discourse_version": "1.0.0",
"assets": {
"font": "assets/awesome.woff2"
},
@@ -72,6 +74,8 @@ describe RemoteTheme do
expect(remote.about_url).to eq("https://www.site.com/about")
expect(remote.license_url).to eq("https://www.site.com/license")
+ expect(remote.theme_version).to eq("1.0")
+ expect(remote.minimum_discourse_version).to eq("1.0.0")
expect(@theme.theme_fields.length).to eq(6)
diff --git a/spec/models/theme_spec.rb b/spec/models/theme_spec.rb
index 1a4b758ee0e..7654f9d9f34 100644
--- a/spec/models/theme_spec.rb
+++ b/spec/models/theme_spec.rb
@@ -57,10 +57,12 @@ describe Theme do
end
- it 'can correctly find parent themes' do
- theme.add_child_theme!(child)
+ it "can automatically disable for mismatching version" do
+ expect(theme.enabled?).to eq(true)
+ theme.create_remote_theme!(remote_url: "", minimum_discourse_version: "99.99.99")
+ expect(theme.enabled?).to eq(false)
- expect(child.dependant_themes.length).to eq(1)
+ expect(Theme.transform_ids([theme.id])).to be_empty
end
it "doesn't allow multi-level theme components" do
@@ -174,30 +176,34 @@ HTML
end
describe ".transform_ids" do
+ let!(:orphan1) { Fabricate(:theme, component: true) }
let!(:child) { Fabricate(:theme, component: true) }
let!(:child2) { Fabricate(:theme, component: true) }
+ let!(:orphan2) { Fabricate(:theme, component: true) }
+ let!(:orphan3) { Fabricate(:theme, component: true) }
+ let!(:orphan4) { Fabricate(:theme, component: true) }
before do
theme.add_child_theme!(child)
theme.add_child_theme!(child2)
end
+ it "returns an empty array if no ids are passed" do
+ expect(Theme.transform_ids([])).to eq([])
+ end
+
it "adds the child themes of the parent" do
sorted = [child.id, child2.id].sort
expect(Theme.transform_ids([theme.id])).to eq([theme.id, *sorted])
- fake_id = [child.id, child2.id, theme.id].min - 5
- fake_id2 = [child.id, child2.id, theme.id].max + 5
-
- expect(Theme.transform_ids([theme.id, fake_id2, fake_id]))
- .to eq([theme.id, fake_id, *sorted, fake_id2])
+ expect(Theme.transform_ids([theme.id, orphan1.id, orphan2.id])).to eq([theme.id, orphan1.id, *sorted, orphan2.id])
end
it "doesn't insert children when extend is false" do
- fake_id = theme.id + 1
- fake_id2 = fake_id + 2
- fake_id3 = fake_id2 + 3
+ fake_id = orphan2.id
+ fake_id2 = orphan3.id
+ fake_id3 = orphan4.id
expect(Theme.transform_ids([theme.id], extend: false)).to eq([theme.id])
expect(Theme.transform_ids([theme.id, fake_id3, fake_id, fake_id2, fake_id2], extend: false))
@@ -466,6 +472,8 @@ HTML
it "can list working theme_translation_manager objects" do
en_translation = ThemeField.create!(theme_id: theme.id, name: "en", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: <<~YAML)
en:
+ theme_metadata:
+ description: "Description of my theme"
group_of_translations:
translation1: en test1
translation2: en test2
@@ -510,6 +518,18 @@ HTML
])
end
+ it "can list internal theme_translation_manager objects" do
+ en_translation = ThemeField.create!(theme_id: theme.id, name: "en", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: <<~YAML)
+ en:
+ theme_metadata:
+ description: "Description of my theme"
+ another_translation: en test4
+ YAML
+ translations = theme.internal_translations
+ expect(translations.map(&:key)).to contain_exactly("theme_metadata.description")
+ expect(translations.map(&:value)).to contain_exactly("Description of my theme")
+ end
+
it "can create a hash of overridden values" do
en_translation = ThemeField.create!(theme_id: theme.id, name: "en", type_id: ThemeField.types[:yaml], target_id: Theme.targets[:translations], value: <<~YAML)
en:
diff --git a/test/javascripts/admin/components/themes-list-test.js.es6 b/test/javascripts/admin/components/themes-list-test.js.es6
index 2a1c58849fc..3842e8d8663 100644
--- a/test/javascripts/admin/components/themes-list-test.js.es6
+++ b/test/javascripts/admin/components/themes-list-test.js.es6
@@ -10,7 +10,8 @@ const components = [1, 2, 3, 4, 5].map(num =>
Theme.create({
name: `Child ${num}`,
component: true,
- parentThemes: [themes[num - 1]]
+ parentThemes: [themes[num - 1]],
+ parent_themes: [1, 2, 3, 4, 5]
})
);