diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 80a574a5d64..abb8e9fc37a 100755 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -160,7 +160,7 @@ def link_to_help(help_entry, link = '? id).first - unless skin - puts "Couldn't find skin with id #{id} to replace" - return nil - end - end - skin + def skin_previews_path + File.join(masters_dir, "previews") + end + + def parent_only_skins + dir = File.join(masters_dir, "parent_only") + Dir["#{dir}/*/*.css"] + end + + def default_skin_preview_path + File.join(skin_previews_path, "default_preview.png") + end + + def ask(message) + print message + $stdin.gets.chomp.strip end def set_parents(skin, parent_names) @@ -31,7 +39,7 @@ namespace :skins do puts "Empty parent name for #{skin.title}" next else - parent_skin = Skin.where(:title => parent_name).first + parent_skin = Skin.where(title: parent_name).first end unless parent_skin puts "Couldn't find parent #{parent_name} to add, skipping" @@ -39,9 +47,13 @@ namespace :skins do end if (parent_skin.role == "site" || parent_skin.role == "override") && skin.role != "override" skin.role = "override" - skin.save or puts "Problem updating skin #{skin.title} to be replacement skin: #{skin.errors.full_messages.join(', ')}" - next + + unless skin.save + puts "Problem updating skin #{skin.title} to be replacement skin: #{skin.errors.full_messages.join(', ')}" + next + end end + p = skin.skin_parents.build(:parent_skin => parent_skin, :position => parent_position) if p.save parent_position += 1 @@ -51,99 +63,101 @@ namespace :skins do end end - def get_user_skins - dir = Skin.site_skins_dir + 'user_skins_to_load' - default_preview_filename = "#{dir}/previews/default_preview.png" - user_skin_files = Dir.entries(dir).select {|f| f.match(/css$/)} - skins = [] - user_skin_files.each do |skin_file| - skins << File.read("#{dir}/#{skin_file}").split(/\/\*\s*END SKIN\s*\*\//) + def load_parent_only_skins(replace:) + parent_only_skins.each do |skin_file| + load_official_css( + filename: skin_file, + replace: replace, + parent_only: true + ) end - skins.flatten! end - desc "Purge user skins parents" - task(:purge_user_skins_parents => :environment) do - get_user_skins.each do |skin_content| - skin = replace_or_new(skin_content) - if skin.new_record? && skin_content.match(/SKIN:\s*(.*)\s*\*\//) - skin = Skin.find_by_title($1.strip) + desc "Purge parents of official skins" + task(purge_official_skin_parents: :environment) do + top_level_skins.each do |skin_file| + skin_content = File.read(skin_file) + + unless skin_content.match(%r{SKIN:\s*(.*)\s*\*/}) + puts "No skin title found for skin #{skin_content}" + next end - skin.skin_parents.delete_all + + skin = Skin.find_by(title: Regexp.last_match(1).strip) + skin&.skin_parents&.delete_all end end - desc "Load user skins" - task(:load_user_skins => :environment) do + desc "Load official skins" + task(load_official_skins: :environment) do replace = ask("Replace existing skins with same titles? (y/n) ") == "y" - Rake::Task['skins:purge_user_skins_parents'].invoke if replace - - author = User.find_by_login("lim") - dir = Skin.site_skins_dir + 'user_skins_to_load' - - skins = get_user_skins - skins.each do |skin_content| - next if skin_content.blank? - - # Determine if we're replacing or creating new - next unless (skin = replace_or_new(skin_content)) - - # set the title and preview - if skin_content.match(/SKIN:\s*(.*)\s*\*\//) - title = $1.strip - if (oldskin = Skin.find_by_title(title)) && oldskin.id != skin.id - if replace - skin = oldskin - else - puts "Existing skin with title #{title} - did you mean to replace? Skipping." - next - end - end - skin.title = title - preview_filename = "#{dir}/previews/#{title.gsub(/[^\w\s]+/, '')}.png" - unless File.exists?(preview_filename) - puts "No preview filename #{preview_filename} found for #{title}" - preview_filename = "#{dir}/previews/default_preview.png" - end - File.open(preview_filename, 'rb') {|preview_file| skin.icon = preview_file} - else - puts "No skin title found for skin #{skin_content}" - next - end - # set the css and make public - skin.css = skin_content - skin.public = true - skin.official = true - skin.author = author unless skin.author + Rake::Task["skins:purge_official_skin_parents"].invoke if replace + load_parent_only_skins(replace: replace) - if skin_content.match(/DESCRIPTION:\s*(.*?)\*\//m) - skin.description = "
#{$1}
" - end - if skin_content.match(/PARENT_ONLY/) - skin.unusable = true - end + top_level_skins.each do |skin_file| + load_official_css( + filename: skin_file, + replace: replace, + preview_path: File.join(File.dirname(skin_file), "preview.png") + ) + end - # make sure we have valid skin now - if skin.save - puts "Saved skin #{skin.title}" - else - puts "Problem with skin #{skin.title}: #{skin.errors.full_messages.join(', ')}" - next - end + # Create Basic Formatting as an official work skin + WorkSkin.basic_formatting + end - # recache any cached skins - if skin.cached? - skin.cache! - end + def load_official_css(filename:, replace: false, parent_only: false, preview_path: default_skin_preview_path) + skin_content = File.read(filename) + return if skin_content.blank? + + unless skin_content.match(%r{SKIN:\s*(.*)\s*\*/}) + puts "No skin title found for skin #{skin_content}" + return + end + title = Regexp.last_match(1).strip - # set parents - if skin_content.match(/PARENTS:\s*(.*)\s*\*\//) - parent_string = $1 + skin = Skin.find_by(title: title) + if skin && !replace + puts "Existing skin with title #{title} - did you mean to replace? Skipping." + return + end + skin ||= Skin.new + + unless File.exist?(preview_path) + puts "No preview filename #{preview_path} found for #{title}" + preview_path = default_skin_preview_path + end + + case skin_content + when /MEDIA: (.*?) ENDMEDIA/ + skin.media = Regexp.last_match(1).split(/,\s?/) + when /MEDIA: (\w+)/ + skin.media = [Regexp.last_match(1)] + end + + skin.title ||= title + skin.author ||= User.find_by(login: "lim") + skin.description = "
#{Regexp.last_match(1)}
" if skin_content.match(%r{DESCRIPTION:\s*(.*?)\*/}m) + skin.filename = filename + skin.css = nil # get_css should load from filename + skin.public = true + skin.role = "user" + skin.unusable = parent_only + skin.official = true + skin.in_chooser = true unless parent_only + skin.icon.attach(io: File.open(preview_path, "rb"), content_type: "image/png", filename: "preview.png") + if skin.save + puts "Saved skin #{skin.title}" + + skin.cache! if skin.cached? + if skin_content.match(%r{PARENTS:\s*(.*)\s*\*/}) + parent_string = Regexp.last_match(1) set_parents(skin, parent_string) end + else + puts "Problem with skin #{skin.title}: #{skin.errors.full_messages.join(', ')}" end - end desc "Load site skins" @@ -189,5 +203,4 @@ namespace :skins do default_id = AdminSetting.default_skin_id Skin.where("id != ?", default_id).update_all(:official => false) end - end diff --git a/public/stylesheets/masters/README.md b/public/stylesheets/masters/README.md new file mode 100644 index 00000000000..15ea62a2dce --- /dev/null +++ b/public/stylesheets/masters/README.md @@ -0,0 +1,54 @@ +# Guide + +This folder contains CSS files for user skins that are loaded into the development database using the provided rake tasks. The skins can be organized into two categories: +- **Top-Level Skins**: Located in the `top_level/` subdirectory. Skins that are added to the skin chooser and subsequently cached. + - Preview images for skins should be placed in the `previews/` subdirectory. If no preview is provided, the default preview image will be used. +- **Parent-Only Skins**: Located in the `parent_only/` subdirectory. Skins that are used as parent components for top-level skins. + +## Rake tasks + +### Load user skins +```bash +rake skins:load_official_skins +``` +This task will: +- Prompt you whether to replace existing skins (`y/n`). +- Load top-level skins from the main directory and adds them to the skin chooser. +- Load parent-only skins from the `parent_only/` directory. + +### Cache skins in skin chooser +```bash +rake skins:cache_chooser_skins +``` +This task caches all skins marked as `in_chooser` and the default skin. + +## Syntax for skin files + +Each CSS file represents a single skin. The title of the skin should be specified at the top of the file: +```css +/* SKIN: My Awesome Skin */ +``` + +### Parent Relationships +Skins can specify parent relationships using the `/* PARENTS: */` comment. Parents can be listed by title or by ID for default site skin components: + +```css +/* PARENTS: Dark Mode - Midsize, Dark Mode - Screen */ +``` +or +```css +/* PARENTS: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 */ +``` + +### Additional Metadata +Skins can include optional metadata: +- **Description**: Add a description using the `/* DESCRIPTION: */` comment. +- **Media Queries**: Specify media queries using the `/* MEDIA: ... */` or `/* MEDIA: ... ENDMEDIA */`comment. + +Example: +```css +/* SKIN: Lorem Ipsum */ +/* DESCRIPTION: This skin serves an important purpose. */ +/* PARENTS: Snow */ +/* MEDIA: only screen and (max-width: 62em) ENDMEDIA */ +``` diff --git a/public/stylesheets/masters/parent_only/dark_mode/README.md b/public/stylesheets/masters/parent_only/dark_mode/README.md new file mode 100644 index 00000000000..6ccc54568b9 --- /dev/null +++ b/public/stylesheets/masters/parent_only/dark_mode/README.md @@ -0,0 +1 @@ +This directory contains the parents of the Dark Mode skin. Please refer to [`public/stylesheets/masters/top_level/dark_mode/README.md`](https://github.com/otwcode/otwarchive/tree/master/public/stylesheets/masters/top_level/dark_mode/README.md) for a full overview. diff --git a/public/stylesheets/masters/dark_mode/dark_mode_midsize_.css b/public/stylesheets/masters/parent_only/dark_mode/dark_mode_midsize_.css similarity index 79% rename from public/stylesheets/masters/dark_mode/dark_mode_midsize_.css rename to public/stylesheets/masters/parent_only/dark_mode/dark_mode_midsize_.css index 91dbf3745dd..ae33efe1d9a 100644 --- a/public/stylesheets/masters/dark_mode/dark_mode_midsize_.css +++ b/public/stylesheets/masters/parent_only/dark_mode/dark_mode_midsize_.css @@ -1,3 +1,6 @@ +/* SKIN: Dark Mode - Midsize */ +/* MEDIA: only screen and (max-width: 62em) ENDMEDIA */ + /* 25-MEDIA-MIDSIZE, 24-MEDIA-NARROW */ #header .user .open a:focus { diff --git a/public/stylesheets/masters/dark_mode/dark_mode_site_screen_.css b/public/stylesheets/masters/parent_only/dark_mode/dark_mode_site_screen_.css similarity index 99% rename from public/stylesheets/masters/dark_mode/dark_mode_site_screen_.css rename to public/stylesheets/masters/parent_only/dark_mode/dark_mode_site_screen_.css index 04a9409104c..d1c8b5ce090 100644 --- a/public/stylesheets/masters/dark_mode/dark_mode_site_screen_.css +++ b/public/stylesheets/masters/parent_only/dark_mode/dark_mode_site_screen_.css @@ -1,3 +1,6 @@ +/* SKIN: Dark Mode - Screen */ +/* MEDIA: screen */ + /* 01-CORE, 02-ELEMENTS: Very basics */ body, diff --git a/public/stylesheets/masters/snow/README.md b/public/stylesheets/masters/parent_only/snow/README.md similarity index 100% rename from public/stylesheets/masters/snow/README.md rename to public/stylesheets/masters/parent_only/snow/README.md diff --git a/public/stylesheets/masters/snow/snow_site_screen_.css b/public/stylesheets/masters/parent_only/snow/snow_site_screen_.css similarity index 95% rename from public/stylesheets/masters/snow/snow_site_screen_.css rename to public/stylesheets/masters/parent_only/snow/snow_site_screen_.css index a882671771b..c4f983f6f3a 100644 --- a/public/stylesheets/masters/snow/snow_site_screen_.css +++ b/public/stylesheets/masters/parent_only/snow/snow_site_screen_.css @@ -1,3 +1,6 @@ +/* SKIN: Snow */ +/* MEDIA: screen */ + body, th, tr:hover, diff --git a/public/stylesheets/masters/previews/default_preview.png b/public/stylesheets/masters/previews/default_preview.png new file mode 100644 index 00000000000..2934d8828ca Binary files /dev/null and b/public/stylesheets/masters/previews/default_preview.png differ diff --git a/public/stylesheets/masters/dark_mode/README.md b/public/stylesheets/masters/top_level/dark_mode/README.md similarity index 100% rename from public/stylesheets/masters/dark_mode/README.md rename to public/stylesheets/masters/top_level/dark_mode/README.md diff --git a/public/stylesheets/masters/top_level/dark_mode/dark_mode.css b/public/stylesheets/masters/top_level/dark_mode/dark_mode.css new file mode 100644 index 00000000000..366b50acffa --- /dev/null +++ b/public/stylesheets/masters/top_level/dark_mode/dark_mode.css @@ -0,0 +1,5 @@ +/* SKIN: Dark Mode */ +/* PARENTS: Dark Mode - Midsize, Dark Mode - Screen */ +/* MEDIA: screen */ + +#unused-selector { content: none; } diff --git a/public/stylesheets/masters/low_vision_default/README.md b/public/stylesheets/masters/top_level/low_vision_default/README.md similarity index 100% rename from public/stylesheets/masters/low_vision_default/README.md rename to public/stylesheets/masters/top_level/low_vision_default/README.md diff --git a/public/stylesheets/masters/low_vision_default/low_vision_default_site_screen_.css b/public/stylesheets/masters/top_level/low_vision_default/low_vision_default_site_screen_.css similarity index 94% rename from public/stylesheets/masters/low_vision_default/low_vision_default_site_screen_.css rename to public/stylesheets/masters/top_level/low_vision_default/low_vision_default_site_screen_.css index 0c4b75754e3..63191e41cbc 100644 --- a/public/stylesheets/masters/low_vision_default/low_vision_default_site_screen_.css +++ b/public/stylesheets/masters/top_level/low_vision_default/low_vision_default_site_screen_.css @@ -1,3 +1,6 @@ +/* SKIN: Low Vision Default */ +/* PARENTS: 1, 2, 4, 5, 7, 8, 9, 10, 11, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32 */ + #outer * { box-shadow: none; } diff --git a/public/stylesheets/masters/reversi/README.md b/public/stylesheets/masters/top_level/reversi/README.md similarity index 100% rename from public/stylesheets/masters/reversi/README.md rename to public/stylesheets/masters/top_level/reversi/README.md diff --git a/public/stylesheets/masters/reversi/reversi_site_screen_.css b/public/stylesheets/masters/top_level/reversi/reversi_site_screen_.css similarity index 99% rename from public/stylesheets/masters/reversi/reversi_site_screen_.css rename to public/stylesheets/masters/top_level/reversi/reversi_site_screen_.css index 0f1abecd46f..a6988948a81 100644 --- a/public/stylesheets/masters/reversi/reversi_site_screen_.css +++ b/public/stylesheets/masters/top_level/reversi/reversi_site_screen_.css @@ -1,3 +1,6 @@ +/* SKIN: Reversi */ +/* MEDIA: screen */ + #outer .region, #footer .group, .post fieldset fieldset, diff --git a/public/stylesheets/masters/snow_blue/README.md b/public/stylesheets/masters/top_level/snow_blue/README.md similarity index 100% rename from public/stylesheets/masters/snow_blue/README.md rename to public/stylesheets/masters/top_level/snow_blue/README.md diff --git a/public/stylesheets/masters/snow_blue/snow_blue_site_screen_.css b/public/stylesheets/masters/top_level/snow_blue/snow_blue_site_screen_.css similarity index 94% rename from public/stylesheets/masters/snow_blue/snow_blue_site_screen_.css rename to public/stylesheets/masters/top_level/snow_blue/snow_blue_site_screen_.css index 04acfac6607..bb74a002646 100644 --- a/public/stylesheets/masters/snow_blue/snow_blue_site_screen_.css +++ b/public/stylesheets/masters/top_level/snow_blue/snow_blue_site_screen_.css @@ -1,3 +1,7 @@ +/* SKIN: Snow Blue */ +/* PARENTS: Snow */ +/* MEDIA: screen */ + #header ul.primary, #footer, .autocomplete .dropdown ul li:hover, diff --git a/public/stylesheets/site/user_skins_to_load/README.txt b/public/stylesheets/site/user_skins_to_load/README.txt deleted file mode 100644 index 3354d9b34b9..00000000000 --- a/public/stylesheets/site/user_skins_to_load/README.txt +++ /dev/null @@ -1,26 +0,0 @@ -This folder holds skins to load into the database. It can include either: - -- one css file per skin -- one css file containing multiple skins marked out by /* END SKIN */ comments - -Put the title at the top: -/* SKIN: My Awesome Skin */ - -If this is an existing skin to replace, add a comment with the skin id: -/* REPLACE: 3 */ - -Each skin can have a comment to list parents: either skins by title or just numbers for any of the current (2.0) site skin components that are part of this skin. -/* PARENTS: 5, 7, 10 */ -or -/* PARENTS: Archive 1.0 */ - -/* SKIN: Paster for complex ones*/ -/* PARENTS: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 */ -/* END SKIN */ - - - -Load them up with the rake task - -rake skins:load_user_skins - diff --git a/script/reset_database.sh b/script/reset_database.sh index 63587bb2f1c..2bc666563d8 100755 --- a/script/reset_database.sh +++ b/script/reset_database.sh @@ -11,6 +11,8 @@ development) bundle install bundle exec rake db:otwseed bundle exec rake skins:load_site_skins + echo "n" | bundle exec rake skins:load_official_skins + bundle exec rake skins:cache_chooser_skins bundle exec rake search:index_tags bundle exec rake search:index_works bundle exec rake search:index_pseuds diff --git a/spec/lib/tasks/skin_tasks.rake_spec.rb b/spec/lib/tasks/skin_tasks.rake_spec.rb index 2451f4c4437..657dfdb8a42 100644 --- a/spec/lib/tasks/skin_tasks.rake_spec.rb +++ b/spec/lib/tasks/skin_tasks.rake_spec.rb @@ -33,3 +33,41 @@ end.to output("\nCouldn't cache #{default_skin.title},#{chooser_skin.title}\n").to_stdout end end + +describe "rake skins:load_official_skins" do + before do + allow($stdin).to receive(:gets).and_return("n") + allow(File).to receive(:read).with(/.*css/).and_return("") + allow(File).to receive(:read).with(/top_level/).and_return( + "/* SKIN: Test Skin */", + "/* SKIN: Child */\n/* PARENTS: Parent */" + ) + allow(File).to receive(:read).with(/parent_only/).and_return("/* SKIN: Parent */\n#unused-selector { content: none; }") + end + + it "creates parent-only skins from the specified directory" do + subject.invoke + + parent_skin = Skin.find_by(title: "Parent") + expect(parent_skin).not_to be nil + expect(parent_skin.unusable).to be true + expect(parent_skin.in_chooser).to be false + end + + it "creates official skins in the specified directory and adds them to skin chooser" do + subject.invoke + + skin = Skin.find_by(title: "Test Skin") + child_skin = Skin.find_by(title: "Child") + + expect(skin).not_to be nil + expect(skin.unusable).to be false + expect(skin.in_chooser).to be true + + expect(child_skin).not_to be nil + expect(child_skin.unusable).to be false + expect(child_skin.in_chooser).to be true + expect(child_skin.skin_parents.length).to eq(1) + expect(child_skin.skin_parents.first.parent_skin.title).to eq("Parent") + end +end